【问题标题】:Reacting to Task completion: `.ContinueWith()` vs `GetAwaiter().OnCompleted()`对任务完成做出反应:`.ContinueWith()` vs `GetAwaiter().OnCompleted()`
【发布时间】:2018-09-29 23:17:03
【问题描述】:

假设我有一个生成intTask,以及一个接受int 的回调:

Task<int> task = ...;
Action<int> f = ...;

现在我想设置它,以便一旦任务完成并返回其整数结果,将使用该整数调用 callfack f - 无需主线程等待任务完成:

  1. 据我了解,典型的解决方案是Task.ContinueWith 方法:

    task.ContinueWith(t => f(t.Result));
    
  2. 但是,也可以为该任务获取TaskAwaiter,并使用其类似事件的界面:

    TaskAwaiter<int> awaiter = task.GetAwaiter();
    awaiter.OnCompleted(() => f(awaiter.GetResult()));
    

现在,我意识到TaskAwaiter 不适合在应用程序代码中普遍使用,而主要存在于await 关键字内部使用。
但是为了加深对TPL的理解,我在想:

解决方案(1)和(2)之间有什么实际区别吗?

例如,

  • ...关于回调将在哪个线程上调用?
  • ...关于在任务或回调中抛出异常时会发生什么?
  • ...还有其他副作用吗?

【问题讨论】:

  • 2 只是 1 的通用版本,适用于多种等待类型...

标签: c# task task-parallel-library continuations


【解决方案1】:

一个区别是

task.ContinueWith(t => f(t.Result));

不会捕获当前同步上下文,例如,在 UI 应用程序中 - 回调将在线程池线程上执行。而

TaskAwaiter<int> awaiter = task.GetAwaiter();
awaiter.OnCompleted(() => f(awaiter.GetResult()));

将捕获同步上下文,并在 UI 线程上执行回调。

当然你也可以对ContinueWith做同样的事情:

task.ContinueWith(r => f(r.Result), TaskScheduler.FromCurrentSynchronizationContext());

但这不是你使用的问题。因此,您的问题中提供的方式至少在这方面有所不同。

在异常表示上也有区别,如果任务出错访问Task.Result会抛出AggregateException(有一个或多个异常作为内部异常),而访问awaiter.GetResult()不会将抛出的异常包裹在AggregateException中并将按原样重新抛出它(如果有多个异常,例如来自Task.WhenAll - 除了一个之外的所有异常都将被忽略,只会抛出一个)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-02-24
    相关资源
    最近更新 更多