C# try/catch(OperationCanceledException)与ContinueWith()的比较

C# try/catch(OperationCanceledException)与ContinueWith()的比较,c#,asynchronous,async-await,C#,Asynchronous,Async Await,Microsoft将通过使用try/catch和OperationCanceledException来处理已取消的任务。同时,可以使用.ContinueWith()将执行的任务包装为continuation,它将吞下操作取消异常而不会抛出。看起来continuation仍然在内部处理异常,但并没有使异常冒泡 给定热执行路径上的可取消任务(接收取消令牌),是否仍建议使用try/catch(OperationCanceledException)或continuation方法 例如: await Ta

Microsoft将通过使用
try/catch
OperationCanceledException
来处理已取消的任务。同时,可以使用
.ContinueWith()
将执行的任务包装为continuation,它将吞下
操作取消异常
而不会抛出。看起来continuation仍然在内部处理异常,但并没有使异常冒泡

给定热执行路径上的可取消任务(接收取消令牌),是否仍建议使用
try/catch(OperationCanceledException)
或continuation方法

例如:

await Task.Delay(delayValue, cts.Token);
可以通过

try
{
  await Task.Delay(delayValue, cts.Token);
}
catch(OperationCanceledException) 
{
  // token triggered cancellation
  return;
}
或通过

var task = await Task.Delay(delayValue, cts.Token).ContinueWith(t => t);
if (task.IsCancelled)
{
  // token triggered cancellation
  return;
}

幕后
wait
会将代码分为启动任务和继续任务。许多等待将导致许多任务。这是使用async/await的最大优势——您不必处理这种复杂性。最好不要把它们混在一起。无论如何,等待很简单,你不能添加太多的选项<另一方面,code>ContinueWith有几个选项。其中包括允许您添加的重载。在不知道最终目的是什么的情况下,您可以同时使用continuation和exception:

var cts = new CancellationTokenSource(TimeSpan.FromSeconds(2));

try
{
    await Task.Delay(TimeSpan.FromSeconds(5), cts.Token)
        .ContinueWith(t => Console.WriteLine("Continued"), TaskContinuationOptions.NotOnCanceled);
    Console.WriteLine("Hihi...");
}
catch (OperationCanceledException)
{
    Console.WriteLine("Cancelled");
}
Console.ReadLine();

严格按照问题而不是动机,我使用BenchmarkDotNet运行了一个基准测试:

[MemoryDiagnoser]
public class CancelBench
{
    private int delayValue = 15;

    private CancellationToken cancellationToken = new CancellationToken(true);

    [Benchmark]
    public async Task<bool> Exception()
    {
        try
        {
            await Task.Delay(delayValue, cancellationToken);
        }
        catch (OperationCanceledException)
        {
            // token triggered cancellation
            return true;
        }
        return false;
    }

    [Benchmark]
    public async Task<bool> ContinueWith()
    {
        var task = await Task.Delay(delayValue, cancellationToken).ContinueWith(t => t);
        if (task.IsCanceled)
        {
            // token triggered cancellation
            return true;
        }
        return false;
    }
}
也就是说,您可以通过避免跳转到线程池来继续进行,从而更快:

    [Benchmark]
    public async Task<bool> ContinueWithSynchronously()
    {
        var task = await Task.Delay(delayValue, cancellationToken).ContinueWith(t => t, TaskContinuationOptions.ExecuteSynchronously);
        if (task.IsCanceled)
        {
            // token triggered cancellation
            return true;
        }
        return false;
    }
NoAsync | 397.7ns | 5.290ns | 4.948ns | 0.0281 |-|-| 148 B|

编辑:我需要花一些时间在最后一个基准测试上,因为我对它分配的内存与异步版本一样多感到非常惊讶。我想知道编译器是否已经在幕后进行了优化(有人说要在.net core上添加该功能,但我很惊讶它已经被移植到.net framework),或者BenchmarkDotNet可能正在进行某些操作。

您是否可以改为使用Wait?显示a以帮助更好地理解问题。@Nkosi问题不是关于在哪里可以使用或不能使用
wait
。这是关于如何处理通过取消令牌取消的任务。虽然提供了最低限度的描述,但我将添加代码片段以使其非常清晰。谢谢。@SeanFeldman-只有当它能够被复制、粘贴和运行时,它才算是一个好东西。@Darem第二个代码段编译并运行得很好。返回的是一项任务,而不是一片空白。@SeanFeldman抱歉,我看错了<代码>任务继续选项。同步执行是一个非常好的优化,谢谢。
    [Benchmark]
    public async Task<bool> ContinueWithSynchronously()
    {
        var task = await Task.Delay(delayValue, cancellationToken).ContinueWith(t => t, TaskContinuationOptions.ExecuteSynchronously);
        if (task.IsCanceled)
        {
            // token triggered cancellation
            return true;
        }
        return false;
    }
ContinueWithSynchronously | 505.2 ns | 2.581 ns | 2.414 ns | 0.0277 | - | - | 148 B |
    [Benchmark]
    public Task<bool> NoAsync()
    {
        return Task.Delay(delayValue, cancellationToken).ContinueWith(t => t.IsCanceled, TaskContinuationOptions.ExecuteSynchronously);
    }
NoAsync | 397.7 ns | 5.290 ns | 4.948 ns | 0.0281 | - | - | 148 B |