对任务完成做出反应:`.ContinueWith()`vs`GetAwaiter()。OnCompleted()`

时间:2018-04-19 12:36:00

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

假设我有一个生成int的{​​{3}},以及接受 int的回调:

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

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

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

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

    TaskAwaiter<int> awaiter = task.GetAwaiter();
    awaiter.OnCompleted(() => f(awaiter.GetResult()));
    
  3. 现在,我意识到TaskAwaiter不适合在应用程序代码中常用,主要存在于await关键字内部使用。
    但为了加深对TPL的理解,我想知道:

    解决方案(1)和(2)之间是否存在实际差异?

    例如,

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

1 个答案:

答案 0 :(得分: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 - 除了一个之外的所有异常将被忽略,只会抛出一个异常。)