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

标签 c# asynchronous async-await

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

在下面的示例中,我想依次运行 2 个异步任务(LoadAsync 和 ComputeAsync),同时我想执行 DoSomethingElse。方法#1 是我熟悉的方法。

方法 #2、#3 和 #4 能否正确实现与 #1 相同的结果?有人可以解释/评论一下幕后有哪些差异吗?谢谢!

方法 #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;

最佳答案

However, I have trouble to master ContinueWith.

最简单的使用方法ContinueWith拼写为“等待”。

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

也就是说,下面将为您提供一些答案,但这些仅用于指导目的,而不是生产代码。

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

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

你的方法 #3 非常接近。如果替换 .Result.GetAwaiter().GetResult() ,您仍然会遇到 TaskScheduler 的问题由 ContinueWith 使用,默认为TaskScheduler.Current ,这可能不是您想要的(在异步代码中,通常不是)。你永远不应该使用ContinueWith不指定 TaskScheduler 。另外,使用 ContinueWith 很奇怪与 async - 如果您使用async,为什么不直接执行方法#1呢?无论如何?

方法 #4 确实会阻塞 ContinueWith 中的线程。 .

如果您想真实地再现方法 #1,您需要:

  1. 防止异常被包裹在 AggregateException 中.
  2. 始终传递显式 TaskSchedulerContinueWith .
  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();
}

关于c# - 使用Continuation链接任务的正确方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54684425/

相关文章:

c# - C# 是否支持函数组合?

javascript - 使用父 Controller 的指令中的异步范围属性

javascript - 在功能组件内执行异步操作

c# - 确定两个数据表包含相同数据的最快方法?

c# - RangeValidator 货币值小数点后不能超过 2 位?

c# - "s"作为格式说明符有什么作用?

python - 从连续运行的 flask 应用程序中异步调用函数

javascript - 实际情况下的异步请求处理如何工作?

node.js - 如果通过 Promise.all 处理数据时发生任何错误,则在 catch 语句中捕获单个记录

c# - 链接 Task.WhenAll()