C# TaskCancellationException如何避免成功控制流上的异常?

C# TaskCancellationException如何避免成功控制流上的异常?,c#,async-await,task-parallel-library,cancellation-token,C#,Async Await,Task Parallel Library,Cancellation Token,在我们的应用程序中,我们经常使用异步/等待和任务。因此它确实使用Task.Run很多,有时使用内置的CancellationToken取消支持 public Task DoSomethingAsync(CancellationToken cancellationToken) { return Task.Run(() => { while (true) { if (cancellationToken.IsCancella

在我们的应用程序中,我们经常使用异步/等待和任务。因此它确实使用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使用异常来控制成功取消,而不仅仅是返回已完成的任务。MS没有遵守“不使用异常控制执行流”规则有什么具体原因吗


我如何避免在一个完全无用的try-catch(TaskCancelledException)块中装箱每个支持取消的方法(很多)

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

首先,您的
任务
可能会返回一个值。当操作被取消时,您返回什么

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

异常会传播。任务取消与此用法中的
Thread.Abort
非常相似-当您发出
Thread.Abort
时,会使用
ThreadAbortException
来确保您一直放回到顶部。否则,您的所有方法都必须检查它们调用的每个方法的结果,检查它们是否被取消,并在需要时返回它们自己——我们已经看到,人们会忽略老派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;
        }
      );
}
当然,应该注意的是,没有人强迫你使用这种取消方法——这只是为了方便。例如,您可以使用自己的放大类型保留取消信息,并手动处理取消。但是,当您开始这样做时,您会发现使用异常处理取消的原因-在命令式代码中执行此操作是一件痛苦的事情,因此您要么会浪费大量精力而一无所获,要么会切换到更实用的编程方式(来吧,我们有cookies!*)


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

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

但是,如果您希望对
TaskCanceledException
行为有更多的控制,并且仍然将逻辑隔离到一个位置,则可以实现扩展方法来扩展处理取消的
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;
    }
}
公共异步任务DoSomethingAsync(CancellationToken CancellationToken)
{
等待任务。运行(()=>
{
while(true)
{
如果(cancellationToken.IsCancellationRequested)中断;
//做些工作
}
}).
WithCancellation(cancellationToken,false);//将取消令牌传递给扩展函数以运行
}   
静态类TaskCacellationHelper
{
私有结构Void{}//仅用于支持TaskCompletionSource类。
带取消的公共静态异步任务(此任务原始任务、取消令牌ct、bool suppressionCancellationException)
{
//创建在取消CancellationToken时完成的任务
var cancelTask=new TaskCompletionSource();
//取消CancellationToken后,完成任务
使用(ct.Register)(
t=>((TaskCompletionSource)t.TrySetResult(new Void()),cancelTask))
{
//创建一个任务,当原始任务或
//取消令牌任务完成
Task any=等待任务.WhenAny(originalTask,cancelTask.Task);
//如果任何任务由于CancellationToken而完成,则抛出OperationCanceledException
if(any==cancelTask.Task)
{
//
if(suppressCancellationExction==false)
{
ct.ThrowIfCancellationRequested();
}
其他的
{
Console.WriteLine(“已取消但禁止例外”);
}
}
}
//等待原始任务。如果取消,您的逻辑将中断while循环
等待原阿尔塔斯克;
}
}

“取消!=成功完成”。取消时,
Task
将返回什么?使用cancellationTokenSource.Cancel()取消的任务实例;将具有TaskStatus.RanToCompletion状态,而不是TaskStatus.cancelled状态。对于返回值为的任务,您是正确的。需要异常。我没想过。我已经回答了类似的问题,不确定这是否是重复的。特别是@S.Dav,这取决于任务是通过请求的
来使用
还是在令牌设置为true时简单地返回。
  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;
    }
}