以取消状态结束的等待任务不会抛出

Mic*_*ael 2 c# asynchronous task-parallel-library

阅读Stephen Toub 所著的基于任务的异步模式我试图了解任务取消是如何工作的。在Await使用基于任务的异步模式部分中,在第 3 段中表示:

如果等待的Task 或Task TResult> 以Canceled 状态结束,则会抛出OperationCanceledException。

我试图在下面的代码中看到它的实际效果。

static void Main(string[] args)
{
   CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
   CancellationToken cancellationToken = cancellationTokenSource.Token;

   Task<int> valueTask = DoStuffAsync(cancellationToken);
   Thread.Sleep(TimeSpan.FromSeconds(1));
   cancellationTokenSource.Cancel();

   Console.WriteLine("value task's status: {0}", valueTask.Status); 
   Console.ReadLine();
}
Run Code Online (Sandbox Code Playgroud)

以及DoStuffAsync()方法

static async Task<int> DoStuffAsync(CancellationToken cancellationToken)
{
    await Task.Delay(TimeSpan.FromSeconds(5), cancellationToken);
    return 42;
}
Run Code Online (Sandbox Code Playgroud)

执行此代码不会引发任何异常,它只会打印:

值任务的状态:已取消

现在,我的期望是在DoStuffAsync()方法中,因为await Task.Delay(...)被取消了,我们已经等待任务以取消状态结束,因此应该抛出异常(根据 TAP 文档的引用),但是如果我放置一个断点Console.ReadLine()并检查valueTask它的状态是否已取消,例外是null

如果我误读了文档,或者我提出的代码没有正确重现案例,有人可以帮助我理解吗?

Me.*_*ame 5

该方法返回任务本身,并且结果本身永远不会被访问。如果您尝试访问valueTask.Result,您将得到一个TaskCanceledException(在 AggregateException 内)。

同样,如果您愿意await valueTask(Main 必须通过异步),您将尝试获取结果,在这种情况下也会引发异常。这是上述段落中描述的行为。

线索是 Task 对象是有效的,但结果不是,因为如果任务被取消,await 之后的代码永远不会执行。例如:

static async Task<int> DoStuffAsync(CancellationToken cancellationToken)
{
    var delay = Task.Delay(5000, cancellationToken);
    Console.WriteLine("before await"); 
    await delay;
    Console.WriteLine("after await"); 
    return 42;
}
Run Code Online (Sandbox Code Playgroud)

如果任务被取消,第二个写入行永远不会执行。只要没有访问到返回的任务的结果DoStuffAsync,该任务就是一个有效的对象,只是被取消了。访问结果将强制运行时承认任务从未完成并引发异常。

如果异步方法不返回任务,您还会收到 TaskCancelled 异常:

static async void Main()
{
   CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
   CancellationToken cancellationToken = cancellationTokenSource.Token;

   DoStuffAsync(cancellationToken);
   Thread.Sleep(TimeSpan.FromSeconds(1));
   cancellationTokenSource.Cancel();
   Console.ReadLine();
}

// Define other methods and classes here
static async void DoStuffAsync(CancellationToken cancellationToken)
{
    await Task.Delay(5000, cancellationToken);
}
Run Code Online (Sandbox Code Playgroud)

由于没有返回可以包含状态的任务对象,因此编译器必须警告执行代码它无法完全运行异步代码并引发异常。