对任务完成的反应:`.ContinueWith()` 与 `GetAwaiter().OnCompleted()`
Reacting to Task completion: `.ContinueWith()` vs `GetAwaiter().OnCompleted()`
假设我有一个生成 int
的 Task
和一个 接受 一个 int
:
的回调
Task<int> task = ...;
Action<int> f = ...;
现在我想设置它,以便一旦任务完成并返回其整数结果,将使用该整数调用 callfack f
– 无需主线程等待任务完成完成:
据我了解,典型的解决方案是 Task.ContinueWith
方法:
task.ContinueWith(t => f(t.Result));
但是,也可以为该任务获取一个 TaskAwaiter
,并使用其类似事件的接口:
TaskAwaiter<int> awaiter = task.GetAwaiter();
awaiter.OnCompleted(() => f(awaiter.GetResult()));
现在,我意识到 TaskAwaiter
并不是为了在应用程序代码中通用,而是主要供 await
关键字在内部使用。
但是为了加深对TPL的理解,我想知道:
解决方案(1)和(2)之间有什么实际区别吗?
例如,
- ...关于将在哪个线程上调用回调?
- ...关于在任务或回调中抛出异常时会发生什么?
- ...还有其他副作用吗?
一个区别是
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
- 除了一个将被忽略,所有异常将被抛出)。
假设我有一个生成 int
的 Task
和一个 接受 一个 int
:
Task<int> task = ...;
Action<int> f = ...;
现在我想设置它,以便一旦任务完成并返回其整数结果,将使用该整数调用 callfack f
– 无需主线程等待任务完成完成:
据我了解,典型的解决方案是
Task.ContinueWith
方法:task.ContinueWith(t => f(t.Result));
但是,也可以为该任务获取一个
TaskAwaiter
,并使用其类似事件的接口:TaskAwaiter<int> awaiter = task.GetAwaiter(); awaiter.OnCompleted(() => f(awaiter.GetResult()));
现在,我意识到 TaskAwaiter
并不是为了在应用程序代码中通用,而是主要供 await
关键字在内部使用。
但是为了加深对TPL的理解,我想知道:
解决方案(1)和(2)之间有什么实际区别吗?
例如,
- ...关于将在哪个线程上调用回调?
- ...关于在任务或回调中抛出异常时会发生什么?
- ...还有其他副作用吗?
一个区别是
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
- 除了一个将被忽略,所有异常将被抛出)。