返回任务时链接任务的正确方法是什么?

Tra*_*uy9 22 c# task-parallel-library

我在C#中使用Tasks非常如此,但是当我尝试从方法中返回一个Task时,我感到很困惑,而且该方法本身会执行多个任务.那么我是否让我的方法启动一个新的任务,然后在那里顺序完成所有事情?用.ContinueWith()来完成这一切是很难的.

例:

public Task<string> GetSomeData(CancellationToken token)
{
    return Task.Factory.StartNew(() =>
    {
        token.ThrowIfCancellationRequested();

        var initialData = GetSomeInteger(token).Result;

        return GetSomeString(initialData, token).Result;
    });
}

public Task<int> GetSomeInteger(CancellationToken token)
{
    return Task<int>.Factory.StartNew(() =>
    {
        return 4;
    }, token);
}

public Task<string> GetSomeString(int value, CancellationToken token)
{
    return Task<string>.Factory.StartNew(() =>
    {
        return value.ToString();
    }, token);
}
Run Code Online (Sandbox Code Playgroud)

我不确定如何编写此方法以使其正确使用任务.我想我觉得应该有一个.ContinueWith在那里或什么的.

可能的修复?

public Task<string> GetSomeData(CancellationToken token)
{
    return GetSomeInteger(token).ContinueWith((prevTask) =>
    {
        return GetSomeString(prevTask.Result, token);
    }, token).Unwrap();
}
Run Code Online (Sandbox Code Playgroud)

Ree*_*sey 26

通常,如果您已经在使用基于任务的方法,通常最好避免启动新任务.链接任务而不是显式阻塞将减少系统的开销,因为它不会让ThreadPool线程等待.

话虽这么说,但是在你做的时候阻止它通常更简单.

请注意,C#5使这更加简单,提供了一个API,可以为您提供以下两者中的最佳功能:

public async Task<string> GetSomeData(CancellationToken token)
{
    token.ThrowIfCancellationRequested();

    var initialData = await SomeOtherMethodWhichReturnsTask(token);

    string result = await initialData.MethodWhichAlsoReturnsTask(token);

    return result;
};
Run Code Online (Sandbox Code Playgroud)

更新后编辑:

鉴于新代码,没有一种简单的方法可以直接链接它ContinueWith.有几种选择.您可以使用Unwrap转换Task<Task<string>>您创建的内容,即:

public Task<string> GetSomeData(CancellationToken token)
{
    Task<Task<string>> task = GetSomeInteger(token)
                               .ContinueWith(t => 
                               {
                                   return GetSomeString(t.Result, token);
                               }, token);
    return task.Unwrap();
}
Run Code Online (Sandbox Code Playgroud)

或者,您可以优雅地处理自己展开TaskCompletionSource<T>:

public Task<string> GetSomeData(CancellationToken token)
{
    var tcs = new TaskCompletionSource<string>();

    Task<int> task1 = GetSomeInteger(token);
    Task<Task<string>> task2 = task1.ContinueWith(t => GetSomeString(t.Result, token));
    task2.ContinueWith(t => tcs.SetResult(t.Result.Result));
    return tcs.Task;
}
Run Code Online (Sandbox Code Playgroud)

这允许整个过程在不创建新任务(绑定线程池线程)的情况下工作,而且不会阻塞.

请注意,您可能希望在取消时添加延续,并在请求取消时使用tcs.SetCancelled.


小智 5

这是我为解决此问题而构建的扩展方法。适用于 .Net 4+

public static Task<TNewResult> ContinueWith<T, TNewResult>(this Task<T> task, Func<Task<T>, Task<TNewResult>> continuationFunction, CancellationToken cancellationToken)
{
    var tcs = new TaskCompletionSource<TNewResult>();
    task.ContinueWith(t => 
    {
        if (cancellationToken.IsCancellationRequested)
        {
            tcs.SetCanceled();
        }
        continuationFunction(t).ContinueWith(t2 => 
        {
            if (cancellationToken.IsCancellationRequested || t2.IsCanceled)
            {
                tcs.TrySetCanceled();
            }
            else if (t2.IsFaulted)
            {
                tcs.TrySetException(t2.Exception);
            }
            else
            {
                tcs.TrySetResult(t2.Result);
            }
        });
    });
    return tcs.Task;
}
Run Code Online (Sandbox Code Playgroud)

  • 如果您还可以通过一个简单的示例发布此扩展方法的用法,将会有所帮助:) (5认同)