我目前正在学习如何正确地使用Task
暴露我们库API的异步部分,以便于客户更容易、更方便地使用。我决定采用使用TaskCompletionSource
的方法,将一个不需要在线程池上调度的Task
包装起来(在这个例子中,基本上只是一个计时器)。尽管这种方法很有效,但取消操作却有一点麻烦。
示例展示了基本用法,在令牌上注册委托,但实际情况比我的情况略微复杂,更重要的是,我不确定该如何处理TaskCanceledException
。 文档表示,只需返回并使任务状态切换为RanToCompletion
,或者抛出OperationCanceledException
(导致任务的结果为Canceled
)即可。然而,这些示例似乎仅涉及或者至少提到通过传递给TaskFactory.StartNew
的委托启动的任务。
目前我的代码(粗略)如下:
public Task Run(IFoo foo, CancellationToken token = default(CancellationToken)) {
var tcs = new TaskCompletionSource<object>();
// Regular finish handler
EventHandler<EventArgs> callback = (sender, args) => tcs.TrySetResult(null);
// Cancellation
token.Register(() => {
tcs.TrySetCanceled();
CancelAndCleanupFoo(foo);
});
RunFoo(foo, callback);
return tcs.Task;
}
(执行期间没有结果,也没有可能的异常;这就是我选择从这里开始,而不是从库中更复杂的地方开始的一个原因。)
当前形式下,当我在TaskCompletionSource
上调用TrySetCanceled
时,如果我等待返回的任务,我总是会得到一个TaskCanceledException
,我的猜测是这是正常行为(希望如此),当我想使用取消功能时,我应该在调用周围包装一个try
/catch
。
如果我不使用TrySetCanceled
,那么最终我将在完成回调中运行,并且任务看起来像是正常完成了。但我猜,如果用户想区分正常完成的任务和被取消的任务,TaskCanceledException
基本上是确保这一点的副作用,对吧?
另外一个我没太明白的地方:文档建议说,任何异常,甚至是与取消相关的异常,都会由TPL包装在AggregateException
中。然而,在我的测试中,我总是直接得到TaskCanceledException
,没有任何包装器。我是漏掉了什么,还是文档写得不太好?
简短版:
- 任务要转换为
Canceled
状态,必须有相应的异常,用户必须在异步调用周围包装一个try
/catch
才能检测到,对吗? - 抛出未包装的
TaskCanceledException
也是正常的行为,我没有做错什么吗?
RunFoo
是做什么的?如何使其异步工作?如何取消它? - Panagiotis KanavosAnimator.Animate(IAnimation)
。它以异步方式运行动画实例(同步方式无论如何都没有意义)。由于它是一个动画,因此没有任何长时间运行的内容,而是每帧要做的工作,在 WPF 中有一个专用事件来注册处理程序。从客户端的角度来看,我认为它符合任务的条件。旧的 API 在Animate
方法中有一个回调参数,用于在动画完成后执行操作,在基于任务的变体中,这将通过等待然后执行清理操作(例如重新启用 UI 中的交互)来完成。 - Joey