第七节:利用CancellationTokenSource实现任务取消和利用CancellationToken类检测取消异常。
一. 传统的线程取消
所谓的线程取消,就是线程正在执行的过程中取消线程任务。
传统的线程取消,是通过一个变量来控制,但是这种方式,在release模式下,被优化从cpu高速缓存中读取,而不是从内存中读取,会造成主线程无法执行这一个bug。
1 { 2 var isStop = false; 3 var thread = new Thread(() => 4 { 5 while (!isStop) 6 { 7 Thread.Sleep(100); 8 Console.WriteLine("当前thread={0} 正在运行", Thread.CurrentThread.ManagedThreadId); 9 } 10 }); 11 thread.Start(); 12 Thread.Sleep(1000); 13 isStop = true; 14 }
PS: 通过上面的代码看可以看出来,传统模式的线程取消,在排除release模式bug的情况下,局限性还是很明显的。比如:当子线程任务取消的那一刻,我想执行另外一项任务;我想延时取消一个线程任务;线程取消的时候抛异常。
上述这几种情况,我们都要借助单独的类来处理。
二. CancellationTokenSource实现任务取消
1. 取消任务的同时触发一个函数
利用Cancel方法、Register注册、source.Token标记取消位来实现。
{ CancellationTokenSource source = new CancellationTokenSource(); //注册一个线程取消后执行的逻辑 source.Token.Register(() => { //这里执行线程被取消后的业务逻辑. Console.WriteLine("-------------我是线程被取消后的业务逻辑---------------------"); }); Task.Run(() => { while (!source.IsCancellationRequested) { Thread.Sleep(100); Console.WriteLine("当前thread={0} 正在运行", Thread.CurrentThread.ManagedThreadId); } }, source.Token); Thread.Sleep(2000); source.Cancel(); }
2. 延时取消
线程的延时取消有两种方式:
方案一:CancelAfter方法。
1 #region 方案一:CancelAfter方法 2 { 3 CancellationTokenSource source = new CancellationTokenSource(); 4 //注册一个线程取消后执行的逻辑 5 source.Token.Register(() => 6 { 7 //这里执行线程被取消后的业务逻辑. 8 Console.WriteLine("-------------我是线程被取消后的业务逻辑---------------------"); 9 }); 10 11 Task.Run(() => 12 { 13 while (!source.IsCancellationRequested) 14 { 15 Thread.Sleep(100); 16 Console.WriteLine("当前thread={0} 正在运行", Thread.CurrentThread.ManagedThreadId); 17 } 18 }, source.Token); 19 20 Thread.Sleep(2000); 21 //4s后自动取消 22 source.CancelAfter(new TimeSpan(0, 0, 0, 4)); 23 } 24 #endregion
方案二:CancellationTokenSource构造函数(不再需要Cancel方法了)。
1 { 2 //4s后自动取消 3 CancellationTokenSource source = new CancellationTokenSource(4000); 4 //注册一个线程取消后执行的逻辑 5 source.Token.Register(() => 6 { 7 //这里执行线程被取消后的业务逻辑. 8 Console.WriteLine("-------------我是线程被取消后的业务逻辑---------------------"); 9 }); 10 11 Task.Run(() => 12 { 13 while (!source.IsCancellationRequested) 14 { 15 Thread.Sleep(100); 16 Console.WriteLine("当前thread={0} 正在运行", Thread.CurrentThread.ManagedThreadId); 17 } 18 }, source.Token); 19 20 Thread.Sleep(2000); 21 }
3. 组合取消
利用CreateLinkedTokenSource构建CancellationTokenSource的组合体,其中任何一个体取消,则组合体就取消。
{ CancellationTokenSource source1 = new CancellationTokenSource(); //source1.Cancel(); CancellationTokenSource source2 = new CancellationTokenSource(); source2.Cancel(); var combineSource = CancellationTokenSource.CreateLinkedTokenSource(source1.Token, source2.Token); Console.WriteLine("s1={0} s2={1} s3={2}", source1.IsCancellationRequested, source2.IsCancellationRequested, combineSource.IsCancellationRequested); }
上述代码,source1和source2中的任何一个取消,combineSource就会被取消。
三. CancellationToken类监控取消
CancellationToken类下ThrowIfCancellationRequested属性,等价于if (XXX.IsCancellationRequested){throw new Exception("报错了");}
只要取消就报错。
1 { 2 CancellationTokenSource source1 = new CancellationTokenSource(); 3 CancellationTokenSource source2 = new CancellationTokenSource(); 4 var combineSource = CancellationTokenSource.CreateLinkedTokenSource(source1.Token, source2.Token); 5 source1.Cancel(); 6 7 //if (combineSource.IsCancellationRequested) 8 //{ 9 // throw new Exception("报错了"); 10 //} 11 12 //等价于上面那句话 13 try 14 { 15 combineSource.Token.ThrowIfCancellationRequested(); 16 } 17 catch (Exception) 18 { 19 Console.WriteLine("报错了"); 20 } 21 22 23 Console.WriteLine("s1={0} s2={1} s3={2}", source1.IsCancellationRequested, 24 source2.IsCancellationRequested, 25 combineSource.IsCancellationRequested); 26 }