【发布时间】:2017-09-26 07:25:23
【问题描述】:
我目前正在学习如何使用 Tasks 正确地公开我们库 API 的异步部分,以便他们可以更轻松、更好地为客户使用。我决定使用the TaskCompletionSource approach 将Task 包裹在它周围,该Task 不会被安排在线程池中(在此实例中无论如何都不需要,因为它基本上只是一个计时器)。这很好用,但现在取消有点让人头疼。
该示例显示了基本用法,在令牌上注册一个委托,但比我的情况稍微复杂一点,更重要的是,我不确定如何处理 TaskCanceledException。 The documentation 表示要么只是返回并将任务状态切换到 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 几乎是确保这一点的副作用,对吧?
还有一点我不太明白:Documentation suggests 任何异常,即使是那些与取消有关的异常,都被 TPL 包装在 AggregateException 中。但是,在我的测试中,我总是直接得到TaskCanceledException,没有任何包装。我在这里遗漏了什么,还是只是记录不充分?
TL;DR:
- 对于转换到
Canceled状态的任务,总是需要相应的异常,用户必须在异步调用周围包裹try/catch才能检测到它,对吗? -
TaskCanceledException被打开也是意料之中的正常现象,我在这里没有做错什么吗?
【问题讨论】:
-
与其将现有的基于事件的 API 与任务包装起来(您的客户可以很好地做到这一点),不如使用基于 task 的 API 并公开事件任何仍然想要它们的人。
RunFoo是做什么的?你如何使它异步工作?你如何取消它? -
PS:取消时不需要例外。接受令牌的方法可以检查
IsCancellationRequested标志并自行取消——不管这意味着什么。例如,不能取消 Http 请求,只能取消等待响应。 -
它通过为
CompositionTarget.OnRendering注册一个事件处理程序来运行动画,因此它实际上不需要在线程池上运行(如前所述,就这个问题而言,它实际上只是一个计时器)。因此它已经异步工作,我通过取消注册OnRendering的事件处理程序来取消它(加上更多的簿记)。所有这些都已经到位。这里的重点是公开一个基于任务的 API而如果可能的话不必重写所有的内部结构。特别是如果事情已经异步工作就好了,只是不公开任何等待 -
好吧,在我的情况下,令牌不会传递给其他任何东西,因为内部还没有使用任务。所以没有人可以检查
IsCancellationRequested。但即便如此,据我了解,你有两个选择:一个是停止做你正在做的事情,这会导致任务运行完成,只是......缺少 actual 完成。另一种是自己抛出异常(例如,通过令牌上的辅助方法),这会导致任务被取消......和 TaskCanceledException。 -
任务抽象出异步执行的单个任务/作业,例如响应事件运行的代码。当一个 single 事件被触发时,一个 single 任务被执行。听起来您正试图将它用于非常不同的事情 - 注册/取消注册事件处理程序?您希望 API 看起来如何?客户会等待任务完成吗?这意味着什么?
标签: c# task-parallel-library task taskcompletionsource