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; } }