TaskCancellationException如何避免成功控制流程中的exception?

在我们的应用程序中,我们使用async / await和Tasks工作了很多。 因此它确实使用Task.Run很多,有时使用内置的CancellationToken取消支持。

 public Task DoSomethingAsync(CancellationToken cancellationToken) { return Task.Run(() => { while (true) { if (cancellationToken.IsCancellationRequested) break; //do some work } }, cancellationToken); } 

如果我现在使用CancellationToken取消执行,则执行会在下一个循环开始时停止,或者如果Task根本没有启动,则会抛出exception(Task.Run中的TaskCanceledException)。 现在的问题是为什么Task.Run使用Exception控制成功取消而不是仅返回已完成的Task。 是否有任何具体原因MS没有坚持“不要使用例外来控制执行流程”规则?

如何在一个完全无用的try catch(TaskCancelledException)块中避免使用Boxing支持取消(很多)的方法?

好吧,你不能在你非常简单的场景中看到差异 – 你实际上并没有使用Task的结果,也不需要通过复杂的调用堆栈传播取消。

首先,您的Task可能会返回一个值。 操作取消后您返回什么?

其次,可能还有其他任务跟随您取消的任务。 您可能希望在方便时通过其他任务传播取消。

例外传播。 在此用法中,任务取消与Thread.Abort非常相似 – 当您发出Thread.Abort ,会使用ThreadAbortException确保您一直放松到顶部。 否则,你的所有方法都必须检查他们调用的每个方法的结果,检查它们是否被取消,并在需要时自行返回 – 我们已经看到人们会忽略老派C中的错误返回值:)

最后,任务取消,就像线程中止一样,是一种特殊情况。 它已经涉及同步,堆栈展开等。

但是,这并不意味着您必须使用try-catch来捕获exception – 您可以使用任务状态。 例如,您可以使用这样的辅助函数:

 public static Task DefaultIfCanceled(this Task @this, T defaultValue = default(T)) { return @this.ContinueWith ( t => { if (t.IsCanceled) return defaultValue; return t.Result; } ); } 

你可以用作哪个

 await SomeAsync().DefaultIfCanceled(); 

当然,应该注意的是,noöne强迫您使用这种取消方法 – 它只是为了方便而提供。 例如,您可以使用自己的放大类型来保留取消信息,并手动处理取消。 但是当你开始这样做时,你会发现使用exception处理取消的原因 – 在命令式代码中执行此操作是一件痛苦的事情,所以你要么浪费很多努力才能获得收益,否则你将转向更实用的编程方式(来吧,我们有cookies!*)。

(*) 免责声明:我们实际上没有cookies。 但你可以自己做!

出于某种目的抛出exception,因为社区中的其他人已经指出了这一点。

但是,如果您希望更多地控制TaskCanceledException行为并且仍然将逻辑隔离到一个地方,您可以实现Extension方法来扩展处理取消的Task ,类似这样的事情 –

  public async Task DoSomethingAsync(CancellationToken cancellationToken) { await Task.Run(() => { while (true) { if (cancellationToken.IsCancellationRequested) break; //do some work } }). WithCancellation(cancellationToken,false); // pass the cancellation token to extension funciton instead to run } static class TaskCacellationHelper { private struct Void { } // just to support TaskCompletionSource class. public static async Task WithCancellation(this Task originalTask, CancellationToken ct, bool suppressCancellationExcetion) { // Create a Task that completes when the CancellationToken is canceled var cancelTask = new TaskCompletionSource(); // When the CancellationToken is canceled, complete the Task using (ct.Register( t => ((TaskCompletionSource)t).TrySetResult(new Void()), cancelTask)) { // Create a Task that completes when either the original or // CancellationToken Task completes Task any = await Task.WhenAny(originalTask, cancelTask.Task); // If any Task completes due to CancellationToken, throw OperationCanceledException if (any == cancelTask.Task) { // if (suppressCancellationExcetion == false) { ct.ThrowIfCancellationRequested(); } else { Console.WriteLine("Cancelled but exception supressed"); } } } // await original task. Incase of cancellation your logic will break the while loop await originalTask; } }