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

时间:2019-02-14 06:30:03

标签: c# asynchronous async-await

searchAll()-async概念很容易理解,但是,我很难掌握await

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

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

方法1-在ContinueWith函数内部使用await

async

方法2-在public async int LoadAndComputeAsync { var data = await LoadAsync(); return await ComputeAsync(data); } Task<int> task1 = LoadAndComputeAsync(); DoSomethingElse(); int result = await task1; 中同步执行

Task.Run

方法3-将Task<int> task2 = Task.Run(() => { var data = LoadAsync().Result; return ComputeAsync(data).Result; }); DoSomethingElse(); int result = await task2; ContinueWithasync一起使用

Unwrap

方法4-在Task<int> task3 = LoadAsync().ContinueWith(async(t) => { var data = t.Result; return await ComputeAsync(data); }).Unwrap(); DoSomethingElse(); int result = await task3; 中同步执行

ContinueWith

2 个答案:

答案 0 :(得分:2)

  

但是,我很难掌握ContinueWith

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

不,认真。 ContinueWith is a low-level API with surprising default behavior。这将使您的代码更复杂,更难以维护,同时没有任何好处。所以我的问题是“为什么?”

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

首先,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,可能不是您想要什么(在异步代码中,通常不是)。在未指定ContinueWith的情况下,切勿使用TaskScheduler。另外,将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 :(得分:0)

  1. 方法2和它通过Task.Run创建线程的方法之间的最大区别 async / await永远不会创建新线程-它在从其调用的同一线程上执行延续。
  2. 方法#1和方法#3几乎相同