26

我非常喜欢在 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);
}

我不确定如何编写此方法以使其正确使用任务。我想我只是觉得那里应该有一个 .ContinueWith 之类的东西。

可能修复??

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

3 回答 3

32

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

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

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

于 2012-08-02T18:22:12.220 回答
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;
}
于 2014-08-24T03:09:51.917 回答
0

是的,一切都将在您的主要任务中按顺序运行。这是因为调用 Result 属性将阻塞当前线程,直到值返回。

于 2012-08-02T18:16:00.893 回答