CancellationTokenSource的行为不符合预期
在这种情况下预期的是,如果用户通过按Enter键取消任务,则ContinueWith
挂钩的另一个任务将运行,但事实并非如此,因为尽管在ContinueWith
显式处理显然不是,但仍然抛出了AggregateException
被执行。
有关下面的任何说明吗?
class Program { static void Main(string[] args) { CancellationTokenSource tokensource = new CancellationTokenSource(); CancellationToken token = tokensource.Token; Task task = Task.Run(() => { while (!token.IsCancellationRequested) { Console.Write("*"); Thread.Sleep(1000); } }, token).ContinueWith((t) => { t.Exception.Handle((e) => true); Console.WriteLine("You have canceled the task"); }, TaskContinuationOptions.OnlyOnCanceled); Console.WriteLine("Press any key to cancel"); Console.ReadLine(); tokensource.Cancel(); task.Wait(); } }
让我们从一些事实开始:
- 当您将
CancellationToken
作为Task.Run
的参数Task.Run
,只有在任务开始运行之前取消它才会生效。 如果任务已在运行,则不会取消。 - 要在任务开始运行后取消任务,您需要使用
CancellationToken.ThrowIfCancellationRequested
,而不是CancellationToken.IsCancellationRequested
。 - 如果任务被取消,则其
Exception
属性不会包含任何exception,并且为null
。 - 如果延续任务由于某种原因未运行,则表示已取消。
- 任务包含来自其自身及其所有子任务的exception(因此,
AggregateException
)。
这就是你的代码中发生的事情:
任务开始运行,因为令牌未被取消。 它将一直运行,直到令牌被取消。 在它结束之后,继续将不会运行,因为它只在前面的任务被取消时运行,而它还没有。 当您Wait
任务时,它将抛出一个带有TaskCanceledException
的AggregateException
,因为取消了延续 (如果您将删除该延续,则exception将消失)。
解:
您需要修复任务以使其实际被取消,并删除(或空检查)exception处理,因为没有exception:
var task = Task.Run(new Action(() => { while (true) { token.ThrowIfCancellationRequested(); Console.Write("*"); Thread.Sleep(1000); } }), token).ContinueWith( t => Console.WriteLine("You have canceled the task"), TaskContinuationOptions.OnlyOnCanceled);
如果您将令牌作为第二个参数传递,则该任务将无法正常继续,因为它确实已被取消。 相反,它会抛出一个OperationCanceledException,它包含在AggregateException中。 这完全是预期的。 现在,如果你没有将令牌传递给任务构造函数,那么你会看到你期望的行为,因为你只是使用令牌作为退出while循环的标志。 在这种情况下,你并没有真正取消任务,你正在退出while循环并正常完成任务。