繁体   English   中英

使用Continuation链接任务的正确方法

[英]The correct way of chaining tasks using Continuation

async await概念很容易理解,但是,我很难掌握ContinueWith

在下面的示例中,我要一个接一个地运行2个异步任务(LoadAsync和ComputeAsync),与此同时,我想执行DoSomethingElse。 方法1是我熟悉的方法。

为了获得与#1相同的结果,方法#2,#3和#4是否正确? 有人可以解释/评论一下幕后的区别吗? 谢谢!

方法#1-在async函数中使用await

public async int LoadAndComputeAsync
{ 
    var data = await LoadAsync();
    return await ComputeAsync(data);
}

Task<int> task1 = LoadAndComputeAsync();
DoSomethingElse();
int result = await task1;

方法2-在Task.Run同步执行

Task<int> task2 = Task.Run(() => {
    var data = LoadAsync().Result;
    return ComputeAsync(data).Result;
});
DoSomethingElse();
int result = await task2;

方法#3-将ContinueWithasyncUnwrap

Task<int> task3 = LoadAsync().ContinueWith(async(t) => {
    var data = t.Result;
    return await ComputeAsync(data);
}).Unwrap();
DoSomethingElse();
int result = await task3;

方法4-在ContinueWith同步执行

Task<int> task4 = LoadAsync().ContinueWith(t => {
    var data = t.Result;
    return ComputeAsync(data).Result;
});
DoSomethingElse();
int result = await task4;

但是,我很难掌握ContinueWith

使用ContinueWith的最简单方法是拼写为“ await”。

不,认真 ContinueWith是具有令人惊讶的默认行为的低级API 这将使您的代码更复杂,更难以维护,同时没有任何好处。 所以我的问题是“为什么?”

也就是说,以下内容将为您提供一些答案,但这些答案仅供参考不用于生产代码。

首先, Task<T>.Result具有不同的异常处理行为; 它将所有异常包装在AggregateException而不是直接引发它们。 这是因为Task<T>最初是为并行编程而不是异步编程而设计的。 但是当添加了async / await时,Microsoft决定只重用现有的Task / Task<T>类型,而不是创建更多异步本机类型。 对于异步代码,替换.Result.GetAwaiter().GetResult()

接下来, async不会将工作排队到线程池中。 Task.Run会。 这是方法2的另一个区别。

您的方法3非常接近。 如果您要更换.Result.GetAwaiter().GetResult()你仍然有问题TaskScheduler通过使用ContinueWith ,默认为TaskScheduler.Current ,你想要什么(在异步代码可能不是,它通常是不)。 在未指定TaskScheduler情况下,切勿使用ContinueWith 另外,将ContinueWithasync一起使用也很奇怪-如果您仍在使用async ,为什么不只是使用方法1?

方法4确实阻止了ContinueWith的线程。

如果要真正复制方法1,则需要:

  1. 防止将异常包装在AggregateException
  2. 始终将一个明确的TaskScheduler传递给ContinueWith
  3. ContinueWith使用其他适当的标志,以使其行为更加异步友好。
  4. 捕获适当的上下文并在该上下文中执行继续。

这是一个例子:

// Original
public async Task<int> LoadAndComputeAsync()
{ 
  var data = await LoadAsync();
  return await ComputeAsync(data);
}

// Using ContinueWith
public Task<int> LoadAndComputeTheHardWayAsync()
{
  var scheduler = SynchronizationContext.Current != null ?
      TaskScheduler.FromCurrentSynchronizationContext() : TaskScheduler.Current;
  return LoadAsync().ContinueWith(t =>
      {
        var data = t.GetAwaiter().GetResult();
        return ComputeAsync(data);
      },
      CancellationToken.None,
      TaskContinuationOptions.DenyChildAttach | TaskContinuationOptions.ExecuteSynchronously,
      scheduler).Unwrap();
}
  1. 方法#2与其他方法(由Task创建线程)之间的最大区别是,运行async / await永远不会创建新线程-它在从其调用的同一线程上执行延续。
  2. 方法#1和方法#3几乎相同

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM