返回Task时,将Task链接起来的正确方法是什么

本文关键字:Task 是什么 方法 链接 返回 起来 | 更新日期: 2024-09-08 03:56:58

我对在C#中使用Tasks很满意,但当我试图从一个方法返回Task时,我会感到困惑,因为该方法将在自己内部执行多个任务。那么,我是不是让我的方法旋转一个新的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);
}

我不确定如何编写此方法以使其正确使用Tasks。我想我只是觉得应该有一个。继续吧。

可能的解决方案??

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

返回Task时,将Task链接起来的正确方法是什么

通常,如果您已经在使用基于任务的方法,则最好尽量避免产生新任务。链接任务而不是显式阻塞将减少系统开销,因为它不会让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;
};

更新后编辑:

考虑到新的代码,没有一种简单的方法可以将其直接与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();
}

或者,您可以使用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;
}

这允许整个进程在不创建新任务(绑定线程池线程)的情况下工作,也不需要阻塞。

请注意,您可能希望在取消时添加continuations,并使用tcs。当请求取消时也设置为Cancelled。

下面是我为解决这个问题而构建的一个扩展方法。在.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;
}

是的,所有内容都将在主任务中按顺序运行。这是因为调用Result属性将阻塞当前线程,直到返回值为止。