参考:Taskcancellation in C# and things you should know about it 微软官方有一篇内容差不多的:Cancellation in Managed Threads
取消一个 Task 的步骤:
- 创建 CancellationToken(下文中以 token 表示),并将其传入 Task
- Task 内时刻监视 token 的取消请求(CancellationRequested)
- Task 检测到取消请求就做出响应
- 在响应的最后 token.ThrowIfCancellationRequested() 抛出 OperationCanceledException
- 在响应的最后 token.ThrowIfCancellationRequested() 抛出 OperationCanceledException
- 调用线程对任务取消进行响应
- 使用 try catch 捕获 OperationCanceledException
- 使用 try catch 捕获 OperationCanceledException
.NET 给我们提供了两个类用于取消 Task:
- CancellationTokenSource:负责创建 token 实例和发送取消请求给从它创建的所有 token 实例
- CancellationToken:传播取消请求
如何传递 token
下面先展示最简单(也是最没用)的方法。
CancellationTokenSource tokenSource = new CancellationTokenSource();CancellationToken token = tokenSource.Token;tokenSource.Cancel();Task.Run(() => Console.WriteLine("Hello from Task"), token);
这种情况 Task.Run 只会在开始任务前检查一下 token。
如果希望在 Task 执行过程中取消它,我们需要将 token 彻底传入 Task。
两种方法:
使用全局变量或变量获取,使得 token 在 Task 内也可见
var token = tokenSource.Token;Task.Run(() =>{var capturedToken = token;...}, token);
将 token 作为状态对象传递,并在 Task 内再转换为 token
var token = tokenSource.Token;Task.Factory.StartNew(stateObject =>{var castedToken = (CancellationToken) stateObject;...}, token, token);
如何监控 token
现在我们在任务内部可以读取 token 了,接下来要关心的就是如何监控 token 的状态。
三种方式:
By polling:轮询 token.IsCancellationRequested
Task.Run(() =>{//Polling boolean propertywhile (!token.IsCancellationRequested) {//Do some work}//Release resources and exit}, token);
Callback registration:注册一旦取消请求被发出就执行的回调方法
Task.Run(async () =>{var webClient = newWebClient();//Registering callback that would cancel downloadingtoken.Register(() => webClient.CancelAsync());var data = await webClient.DownloadDataTaskAsync("http://www.google.com/");}, token);
等待 token.WaitHandle
- WaitHandle 将在 token 取消时发出信号
- WaitHandle.WaitAny 可以等待并接收它发出的信号
var autoResetEvent = newAutoResetEvent(false);//Some code...Task.Run(() =>{var handleArray = new[] {autoResetEvent, token.WaitHandle};//Waiting on wait handle to signal firstvar finishedId = WaitHandle.WaitAny(handleArray);if (finishedId == Array.IndexOf(handleArray, token.WaitHandle)){//If token wait handle was first to signal then exitreturn;}//Continue working...}, token);
- WaitHandle 将在 token 取消时发出信号
注:可以通过创建 LinkedTokenSource 同时监控多个 token。
CancellationTokenSource linkedToukenSource = CancellationTokenSource.CreateLinkedTokenSource(firstToken, secondToken);CancellationToken lindkedToken = linkedToukenSource.Token;
如何取消 Task
知道如何传递和监控 token 后,剩下的就是如何发送取消请求彻底取消 Task 了。
几种发送 cancellation 请求的方法:
//#1 在一定时间后发送取消请求tokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(2));//#2 在一定时间后发送取消请求tokenSource.CancelAfter(TimeSpan.FromSeconds(2));//#3 直接发送取消请求tokenSource.Cancel();//ortokenSource.Cancel(true);
当我们取消任务时,它会隐式调用当前线程所有注册了的回调函数。如果回调函数抛出了异常,这些异常将被包装为一个 AggregateException(异常集合,里面有许多子异常)并将其抛给调用者。
因为回调函数是依次执行的,所以通过 Cancel 可选的 bool 参数,你可以指定这些回调函数遇到异常时的行为。
- true:异常马上抛出,后面的回调函数不再执行
false:所有注册了的回调函数都执行,它们抛出的异常包装为一个AggregateException
当在 task 内检测到 cancellation request 后,通常的做法是抛出 OperationCanceledException,让 task 进入 canceled 状态。Task.Run(() =>{while (true){//Do some workif (token.IsCancellationRequested){token.ThrowIfCancellationRequested();}}}, token);
