在任务执行委托中延迟取消支持的正确方法是什么?
What is the proper way to delay with cancellation support inside of a task execution delegate?
我没有在 MSDN 或此处看到任何关于如何完成此操作的具体提及。用例有点模糊,但我怀疑仍然有效。
var cancel = new CancellationTokenSource();
var task = Task.Factory.StartNew(() => { Task.Delay(1000, cancel.Token).Wait(); }, cancel.Token);
cancel.CancelAfter(100);
task.Wait();
上面的代码将在 100 毫秒后尝试取消包含 detached 子延迟任务的 task
,并且等待 task
完成,这将生成一个 AggregateException
(由于取消)。这样做的问题是 task
出现故障而不是被取消。这是预期的行为,因为延迟任务未附加到父级 task
,即使两者共享相同的取消令牌。
我的问题具体涉及如何将 Task.Delay
附加到已经 运行 的任务。如果您有权访问父任务,甚至可以这样做吗?如果无法访问父任务实例,或者无法访问父任务实例,那么处理这种情况的正确方法是什么?
我能想到的最好的解决方法是将延迟任务的 Wait
包装在 try/finally 块中,并明确尝试冒泡取消任务。
try { Task.Delay(1000, cancel.Token).Wait(); } finally { cancel.Token.ThrowIfCancellationRequested(); }
虽然有效,但感觉不太对,但我不确定是否有更好的方法来实现这一点。期望的结果是,如果发生取消,父任务将转到 Canceled
而不是 Faulted
。因此,如果取消的起源发生在分离的子任务中,则父任务仍应转换为 Canceled
.
注意:我在这里故意遗漏了async/await,只是因为它似乎没有改变问题或结果。如果不是这种情况,请提供示例。
因此,当 OperationCanceledException
被抛出并在其中未被捕获并且其关联的 CancellationToken
被取消时,任务被视为已取消。
在你的例子中,抛出的异常是AggregateException
,包含一个TaskCanceledException
(这是一个OperationCanceledException
)而不是TaskCanceledException
直接。
有一种简单的方法可以解决这个问题。您可以使用 task.GatAwaiter().GetResult()
,而不是使用 Task.Wait
同步阻塞,后者将任何异常包装在 AggregateException
包装器中。这就是 await
在 async-await
中使用的内容。它抛出原始异常,如果有多个异常,它抛出第一个异常:
var cancel = new CancellationTokenSource();
var task = Task.Factory.StartNew(() => { Task.Delay(1000, cancel.Token).GetAwaiter().GetResult(); }, cancel.Token);
cancel.CancelAfter(100);
task.Wait();
如果你使用 async-await
你就不会遇到这个问题,因为不像 Task.Wait
它会抛出 TaskCanceledException
本身:
var cancel = new CancellationTokenSource();
var task = Task.Run(() => Task.Delay(1000, cancel.Token), cancel.Token);
cancel.CancelAfter(100);
task.Wait();
我假设这只是一个例子。实际生产代码不应该与此类似,因为您在异步操作上同步阻塞,而异步操作又在异步操作上同步阻塞。
我没有在 MSDN 或此处看到任何关于如何完成此操作的具体提及。用例有点模糊,但我怀疑仍然有效。
var cancel = new CancellationTokenSource();
var task = Task.Factory.StartNew(() => { Task.Delay(1000, cancel.Token).Wait(); }, cancel.Token);
cancel.CancelAfter(100);
task.Wait();
上面的代码将在 100 毫秒后尝试取消包含 detached 子延迟任务的 task
,并且等待 task
完成,这将生成一个 AggregateException
(由于取消)。这样做的问题是 task
出现故障而不是被取消。这是预期的行为,因为延迟任务未附加到父级 task
,即使两者共享相同的取消令牌。
我的问题具体涉及如何将 Task.Delay
附加到已经 运行 的任务。如果您有权访问父任务,甚至可以这样做吗?如果无法访问父任务实例,或者无法访问父任务实例,那么处理这种情况的正确方法是什么?
我能想到的最好的解决方法是将延迟任务的 Wait
包装在 try/finally 块中,并明确尝试冒泡取消任务。
try { Task.Delay(1000, cancel.Token).Wait(); } finally { cancel.Token.ThrowIfCancellationRequested(); }
虽然有效,但感觉不太对,但我不确定是否有更好的方法来实现这一点。期望的结果是,如果发生取消,父任务将转到 Canceled
而不是 Faulted
。因此,如果取消的起源发生在分离的子任务中,则父任务仍应转换为 Canceled
.
注意:我在这里故意遗漏了async/await,只是因为它似乎没有改变问题或结果。如果不是这种情况,请提供示例。
因此,当 OperationCanceledException
被抛出并在其中未被捕获并且其关联的 CancellationToken
被取消时,任务被视为已取消。
在你的例子中,抛出的异常是AggregateException
,包含一个TaskCanceledException
(这是一个OperationCanceledException
)而不是TaskCanceledException
直接。
有一种简单的方法可以解决这个问题。您可以使用 task.GatAwaiter().GetResult()
,而不是使用 Task.Wait
同步阻塞,后者将任何异常包装在 AggregateException
包装器中。这就是 await
在 async-await
中使用的内容。它抛出原始异常,如果有多个异常,它抛出第一个异常:
var cancel = new CancellationTokenSource();
var task = Task.Factory.StartNew(() => { Task.Delay(1000, cancel.Token).GetAwaiter().GetResult(); }, cancel.Token);
cancel.CancelAfter(100);
task.Wait();
如果你使用 async-await
你就不会遇到这个问题,因为不像 Task.Wait
它会抛出 TaskCanceledException
本身:
var cancel = new CancellationTokenSource();
var task = Task.Run(() => Task.Delay(1000, cancel.Token), cancel.Token);
cancel.CancelAfter(100);
task.Wait();
我假设这只是一个例子。实际生产代码不应该与此类似,因为您在异步操作上同步阻塞,而异步操作又在异步操作上同步阻塞。