9

在我们的应用程序中,我们经常使用 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.Run 中的 TaskCanceledException)。现在的问题是为什么 Task.Run 使用 Exception 来控制成功取消,而不是仅仅返回一个已完成的 Task。MS 是否有任何具体原因不遵守“不要使用异常来控制执行流程”规则?

以及如何避免在完全无用的 try catch (TaskCancelledException) 块中对每个支持取消的方法(很多)进行装箱?

4

2 回答 2

7

好吧,您在非常简单的场景中看不到真正的区别-您实际上并没有使用 的结果Task,并且您不需要通过复杂的调用堆栈传播取消。

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

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

异常传播。任务取消与此用法几乎相同Thread.Abort- 当您发出 a 时Thread.Abort, aThreadAbortException用于确保您一直放松回到顶部。否则,您的所有方法都必须检查它们调用的每个方法的结果,检查它们是否被取消,并在需要时返回自身——我们已经看到人们会忽略老式 C 中的错误返回值 :)

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

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

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

          return t.Result;
        }
      );
}

您可以将其用作

await SomeAsync().DefaultIfCanceled();

当然,应该注意的是 noöne 强迫你使用这种取消方法——它只是为了方便而提供的。例如,您可以使用自己的放大类型来保存取消信息,并手动处理取消。但是当您开始这样做时,您会发现使用异常处理取消的原因 - 在命令式代码中这样做很痛苦,因此您要么浪费大量精力而没有收获,要么您将切换到更实用的编程方式(来吧,我们有 cookie!*)。

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

于 2015-12-15T10:20:53.017 回答
0

正如社区中的其他人已经指出的那样,抛出异常是有目的的。

但是,如果您希望对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<Void>();
        // When the CancellationToken is canceled, complete the Task
        using (ct.Register(
        t => ((TaskCompletionSource<Void>)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;
    }
}
于 2015-12-16T06:21:21.750 回答