等待任务结束于已取消状态不会抛出

时间:2016-11-23 13:12:18

标签: c# asynchronous task-parallel-library

由Stephen Toub阅读Task-based Asynchronous Pattern我试图了解取消对任务的影响。 在 Await 下的使用基于任务的异步模式部分中,在第3段中说:

  

如果任务或任务TResult>期待已在取消状态结束了   将抛出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();
}

DoStuffAsync()方法

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

执行此代码不会抛出任何异常,只会打印:

  

价值任务的状态:已取消

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

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

3 个答案:

答案 0 :(得分:3)

该方法返回任务本身,永远不会访问结果本身。如果您尝试访问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;
}

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

如果异步方法没有返回任务,你也会得到一个TaskCancelled Exception:

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

因为没有要返回的任务对象可以包含状态,所以编译器必须警告执行代码它无法完全运行异步代码并抛出异常。

答案 1 :(得分:2)

当您尝试从此任务中获得结果时,您将获得OperationCanceledException

尝试在Main方法的末尾添加此行:

valueTask.GetAwaiter().GetResult();

您将从TaskCanceledException获得OperationCanceledExceptionawait已取消的任务也将TaskCanceledException投放。

您也可以使用valueTask.ResultvalueTask.Wait(),但他们会将AggregateException与单个成员TaskCanceledException一起投放。

为了更好地了解await中发生的事情,了解它如何在tryroslyn转换为状态机可能会有用。

答案 2 :(得分:0)

修改:此答案适用于您希望使用TaskCanceledException内部Task的情况,但您的问题是关于将其置于其外部但是它可能仍然对某些人有用。 ; - )

TaskDoAsyncStuff函数)中,您必须检查CancellationToken,然后自行抛出TaskCanceledException或者只需拨打ThrowIfCancellationRequested()即可。