.Net 并行编程系列之 Task取消
在Task运行过程中,我们可以通过.Net 4中的内置方法来取消Task的运行。
创建一个可取消的Task需要用到下面的一些对象:
1.System.Threading.CancellationTokenSource实例
CancellationTokenSource tokenSource = new CancellationTokenSource();
2.通过CancellationTokenSource.Token属性获得一个取消令牌
CancellationToken token = tokenSource.Token;
3.创建Task对象,并且在构造函数传入Action(或者Action<T>)委托作为第一个参数,CancellationToken作为第二个参数(重要)
Task task = new Task(() => { // do something }, token);
task.Start();
4.创建Task对象也可以通过调用System.Threading.Tasks.TaskFactory 类提供的静态方法
Task task = Task.Factory.StartNew(() => { // do something ...... }, token);
如果想要取消Task的运行,除了要调用CancellationTokenSource实例的Cancel()方法之外,我们的Action委托中还需要检测CancellationToken的取消状态并编写相应代码(抛出异常)来阻止Task的运行。
可以通过以下方式来检测Task取消状态:
1.通过轮询的方式检测CancellationToken取消标记,该操作类似于轮询异步操作的IAsyncResult.IsCompleted状态,也是通过在循环中判断CancellationToken.IsCancellationRequested属性来检测Task是否被取消,如果为True则在Action委托中抛出异常来取消继续运行Task。
static void Main(string[] args) { CancellationTokenSource tokenSource = new CancellationTokenSource(); CancellationToken token = tokenSource.Token; Task task = new Task(() => { while (true) { if(token.IsCancellationRequested) { // 释放资源操作等等... throw new OperationCanceledException(token); } Console.Write("."); Thread.Sleep(100); } }, token); Console.WriteLine("Task is Running."); Console.WriteLine("Press anykey to cancel task.");
task.Start();
Console.ReadKey(true); Console.WriteLine(); Console.WriteLine("Cancelling task."); tokenSource.Cancel();
Console.WriteLine("Main method complete."); Console.WriteLine("Press enter to finish."); Console.ReadLine(); }
如果不需要释放系统资源,那么可以直接调用CancellationToken.ThrowIfCancellationRequested()方法,其实现如下:
[__DynamicallyInvokable] public void ThrowIfCancellationRequested() { if (this.IsCancellationRequested) { this.ThrowOperationCanceledException(); } }
示例:
while (true) { token.ThrowIfCancellationRequested(); Console.Write("."); Thread.Sleep(100); }
2.通过委托(Delegate)来检测Task是否取消,注册一个在取消CancellationToken时调用的委托,当CancellationTokenSource发送取消请求时,该委托即会运行,我们可以在委托方法中实现通知功能等等。
static void Main(string[] args) { CancellationTokenSource tokenSource = new CancellationTokenSource(); CancellationToken token = tokenSource.Token; Task task = new Task(() => { while (true) { token.ThrowIfCancellationRequested(); Console.Write("."); Thread.Sleep(100); } }, token); token.Register(() => { Console.WriteLine("The delegate is triggered."); }); Console.WriteLine("Task is Running."); Console.WriteLine("Press anykey to cancel task."); task.Start(); Console.ReadKey(true); Console.WriteLine(); Console.WriteLine("Cancelling task."); tokenSource.Cancel(); Console.WriteLine("Main method complete."); Console.WriteLine("Press enter to finish."); Console.ReadLine(); }
3.用WaitHandle来检测Task是否取消,当在CancellationToken.WaitHandle上调用WaitOne()方法时,会阻止当前线程执行,直到该CancellationToken接收到取消请求标记时,被token阻止的Task线程才会释放并继续执行。
多个Task实例使用同一个CancellationToken时,当CancellationToken接收到取消请求标记时,所有在构造函数中使用该token实例化的Task都会被取消。WaitOne(int millisecondsTimeout)可以使用等待毫秒数作为参数,超过等待时间将会释放阻止的线程。
不管WaitOne(int millisecondsTimeout)设置多长的等待时间,只要CancellationToken接收到取消请求标记时Task都会取消,而如果使用Thread.Sleep(100000)进行线程等待时,那么即使CancellationToken接收到取消请求标记,该Task也会等到Thread.Sleep执行完成才会Cancel。
static void Main(string[] args) { CancellationTokenSource tokenSource = new CancellationTokenSource(); CancellationToken token = tokenSource.Token; Task task = new Task(() => { while (true) { token.ThrowIfCancellationRequested(); Console.Write("."); Thread.Sleep(100); } }, token); Task task1 = new Task(() => { token.WaitHandle.WaitOne(); Console.WriteLine("WaitHandle released."); }, token); Console.WriteLine("Task is Running."); Console.WriteLine("Press anykey to cancel task."); task.Start(); task1.Start(); Console.ReadKey(true); Console.WriteLine(); Console.WriteLine("Cancelling task."); tokenSource.Cancel();
Console.WriteLine("Main method complete."); Console.WriteLine("Press enter to finish."); Console.ReadLine(); }
4.通过Task的IsCancelled属性来判断Task是否被取消,如果Task实例化时构造函数没有传入CancellationToken对象,则取消Task运行之后通过Task.IsCanceled属性获取到的值还是False而不是True。Task.ContinueWith()方法如果没有传入CancellationToken对象,则Task即使是取消执行也会继续执行Task.ContinueWith()方法的Action委托,如果传入与Task相同的CancellationToken对象,则Task取消执行后Task.ContinueWith()方法中的Action委托也不会继续执行。
{ CancellationTokenSource tokenSource = new CancellationTokenSource(); CancellationToken token = tokenSource.Token; Task task = new Task(() => { try { Console.WriteLine("Task: Running"); Thread.Sleep(5000); Console.WriteLine("Task: ThrowIfCancellationRequested"); token.ThrowIfCancellationRequested(); Thread.Sleep(2000); Console.WriteLine("Task: Completed"); } catch (Exception exception) { Console.WriteLine("Task: " + exception.GetType().Name); throw; } }, token); task.ContinueWith(t => Console.WriteLine("ContinueWith: tokenSource.IsCancellationRequested = {0}, task.IsCanceled = {1}, task.Exception = {2}", tokenSource.IsCancellationRequested, t.IsCanceled, t.Exception == null ? "null" : t.Exception.GetType().Name)); task.Start(); Thread.Sleep(1000); Console.WriteLine("Main: Cancel"); tokenSource.Cancel(); try { Console.WriteLine("Main: Wait"); task.Wait(); } catch (Exception exception) { Console.WriteLine("Main: Catch " + exception.GetType().Name); } Console.WriteLine("Main: task.IsCanceled = {0}", task.IsCanceled); Console.WriteLine("Press any key to exit..."); Console.ReadKey(true); }
上一篇: JavaScript 中创建私有成员