Task的取消
上篇文章主要讲述了如何创建一个task,本篇文章主要讲述如何取消一个task。
本篇的主要内容如下:
1、通过轮询方式检测Task是否被取消
2、用委托delegate来检测Task是否被取消
3、用Wait Handle检测Task是否被取消
4、取消多个Task
5、创建组合的取消Task的Token
6、判断一个Task是否已被取消了
创建一个取消Task一般要进行如下几步:
第一步,创建System.Threading.CancellationTokenSource的一个实例:
// create the cancellation token source CancellationTokenSource tokenSource = new CancellationTokenSource();
第二步,通过CancellationTokenSource.Token属性获得一个System.Threading.CancellationToken:
CancellationToken token = tokenSource.Token;
第三步,创建一个新的Task或者Task<T>,并且在构造函数传入Action或者Action<object>的委托作为第一个参数,传入CancellationToken作为第二个参数:
Task task = new Task(new Action(printMessage), token);
第四步,启动Task
task.Start();
上面的步骤和我们之前介绍的创建一个Task的代码几乎一样,只是在构造函数中多传入了一个参数。
第五步,如果想要取消一个Task的运行,只要调用CancellationToken实例的Cancel()方法就可以了。
tokenSource.Cancel();
有点要特别注意的,当我们调用了Cancel()方法之后,.NET Framework不会强制性的去关闭运行的Task。
我们自己必须去检测之前在创建Task时候传入的那个CancellationToken。
我们在创建Task是传入CancellationToken到构造函数,其实这个CancellationToken就是.NET Framework用来避免我们再次运行已经被取消的Task,可以说就是一个标志位。
1、通过轮询方式检测Task是否被取消
在很多Task内部都包含了循环,用来处理数据。我们可以在循环中通过CancellationToken的IsCancellationRequest属性来检测task是否被取消了。如果这个属性为true,那么我们就得跳出循环,并且释放task所占用的资源(如数据库资源,文件资源等).
我们也可以在task运行体中抛出System.Threading.OperationCanceledException来取消运行的task。
using System; using System.Threading; using System.Threading.Tasks; namespace 通过轮询方式取消Task { internal class Program { static void Main(string[] args) { CancellationTokenSource tokenSource = new CancellationTokenSource(); CancellationToken token = tokenSource.Token; Task task = new Task(() => { for (int i = 0; i < int.MaxValue; i++) { if (token.IsCancellationRequested) { Console.WriteLine("task cancell detected"); throw new OperationCanceledException(token);//跳出for循环 //break; } else { Console.WriteLine($"int value is {i}"); } Thread.Sleep(100); } }, token); // wait for input before we start the task Console.WriteLine("Press enter to start task"); Console.WriteLine("Press enter again to cancel task"); Console.ReadLine(); // start the task task.Start(); // read a line from the console. Console.ReadLine(); // cancel the task Console.WriteLine("Cancelling task"); tokenSource.Cancel(); // wait for input before exiting Console.WriteLine("Main method complete. Press enter to finish."); Console.ReadLine(); } } }
2、用委托delegate来检测Task是否被取消
我们可以在注册一个委托到CancellationToken中,这个委托的方法在CancellationToken.Cancel()调用之前被调用。
我们可以用这个委托中的方法来作为一个检测task是否被取消的另外一个可选的方法,因为这个方法是在Cancel()方法被调用之前就调用的,所以这个委托中的方法可以检测task是否被cancel了,也就是说,只要这个委托的方法被调用,那么就说这个CancellationToken.Cancel()方法被调用了,而且在这个委托的方法中我们可以做很多的事情,如通知用户取消操作发生了。
下面的代码给出了一个例子。
static void Main(string[] args) { // create the cancellation token source CancellationTokenSource tokenSource = new CancellationTokenSource(); // create the cancellation token CancellationToken token = tokenSource.Token; // create the task Task task = new Task(() => { for (int i = 0; i < int.MaxValue; i++) { if (token.IsCancellationRequested) { Console.WriteLine("Task cancel detected"); throw new OperationCanceledException(token); } else { Console.WriteLine("Int value {0}", i); } } }, token); // register a cancellation delegate token.Register(() => { Console.WriteLine(">>>>>> Delegate Invoked\n"); }); // wait for input before we start the task Console.WriteLine("Press enter to start task"); Console.WriteLine("Press enter again to cancel task"); Console.ReadLine(); // start the task task.Start(); // read a line from the console. Console.ReadLine(); // cancel the task Console.WriteLine("Cancelling task"); tokenSource.Cancel(); // wait for input before exiting Console.WriteLine("Main method complete. Press enter to finish."); Console.ReadLine(); }
3、用Wait Handle检测Task是否被取消
第三种方法检测task是否被cancel就是调用CancellationToken.WaitHandle属性。对于这个属性的详细使用,在后续的文章中会深入的讲述,在这里主要知道一点就行了:CancellationToken的WaitOne()方法会阻止task的运行,只有CancellationToken的cancel()方法被调用后,这种阻止才会释放。
在下面的例子中,创建了两个task,其中task2调用了WaitOne()方法,所以task2一直不会运行,除非调用了CancellationToken的Cancel()方法,所以WaitOne()方法也算是检测task是否被cancel的一种方法了。
static void Main(string[] args) { // create the cancellation token source CancellationTokenSource tokenSource = new CancellationTokenSource(); // create the cancellation token CancellationToken token = tokenSource.Token; // create the task Task task1 = new Task(() => { for (int i = 0; i < int.MaxValue; i++) { if (token.IsCancellationRequested) { Console.WriteLine("Task cancel detected"); throw new OperationCanceledException(token); } else { Console.WriteLine("Int value {0}", i); } } }, token); // create a second task that will use the wait handle Task task2 = new Task(() => { // wait on the handle token.WaitHandle.WaitOne(); // write out a message Console.WriteLine(">>>>> Wait handle released"); }); // wait for input before we start the task Console.WriteLine("Press enter to start task"); Console.WriteLine("Press enter again to cancel task"); Console.ReadLine(); // start the tasks task1.Start(); task2.Start(); // read a line from the console. Console.ReadLine(); // cancel the task Console.WriteLine("Cancelling task"); tokenSource.Cancel(); // wait for input before exiting Console.WriteLine("Main method complete. Press enter to finish."); Console.ReadLine(); }
4、取消多个Task
static void Main(string[] args) { // create the cancellation token source CancellationTokenSource tokenSource = new CancellationTokenSource(); // create the cancellation token CancellationToken token = tokenSource.Token; // create the tasks Task task1 = new Task(() => { for (int i = 0; i < int.MaxValue; i++) { token.ThrowIfCancellationRequested(); Console.WriteLine("Task 1 - Int value {0}", i); } }, token); Task task2 = new Task(() => { for (int i = 0; i < int.MaxValue; i++) { token.ThrowIfCancellationRequested(); Console.WriteLine("Task 2 - Int value {0}", i); } }, token); // wait for input before we start the tasks Console.WriteLine("Press enter to start tasks"); Console.WriteLine("Press enter again to cancel tasks"); Console.ReadLine(); // start the tasks task1.Start(); task2.Start(); // read a line from the console. Console.ReadLine(); // cancel the task Console.WriteLine("Cancelling tasks"); tokenSource.Cancel(); // wait for input before exiting Console.WriteLine("Main method complete. Press enter to finish."); Console.ReadLine(); }
5、创建组合的取消Task的Token
我们可以用CancellationTokenSource.CreateLinkedTokenSource()方法来创建一个组合的token,这个组合的token有很多的CancellationToken组成。主要组合token中的任意一个token调用了Cancel()方法,那么使用这个组合token的所有task就会被取消。代码如下:
static void Main(string[] args) { // create the cancellation token sources CancellationTokenSource tokenSource1 = new CancellationTokenSource(); CancellationTokenSource tokenSource2 = new CancellationTokenSource(); CancellationTokenSource tokenSource3 = new CancellationTokenSource(); // create a composite token source using multiple tokens CancellationTokenSource compositeSource = CancellationTokenSource.CreateLinkedTokenSource( tokenSource1.Token, tokenSource2.Token, tokenSource3.Token); // create a cancellable task using the composite token Task task = new Task(() => { // wait until the token has been cancelled compositeSource.Token.WaitHandle.WaitOne(); // throw a cancellation exception throw new OperationCanceledException(compositeSource.Token); }, compositeSource.Token); // start the task task.Start(); // cancel one of the original tokens tokenSource2.Cancel(); // wait for input before exiting Console.WriteLine("Main method complete. Press enter to finish."); Console.ReadLine(); }
6、判断一个Task是否已被取消了
可以使用Task的IsCancelled属性来判断task是否被取消了。代码如下:
static void Main(string[] args) { // create the cancellation token source CancellationTokenSource tokenSource1 = new CancellationTokenSource(); // create the cancellation token CancellationToken token1 = tokenSource1.Token; // create the first task, which we will let run fully Task task1 = new Task(() => { for (int i = 0; i < 10; i++) { token1.ThrowIfCancellationRequested(); Console.WriteLine("Task 1 - Int value {0}", i); } }, token1); // create the second cancellation token source CancellationTokenSource tokenSource2 = new CancellationTokenSource(); // create the cancellation token CancellationToken token2 = tokenSource2.Token; // create the second task, which we will cancel Task task2 = new Task(() => { for (int i = 0; i < int.MaxValue; i++) { token2.ThrowIfCancellationRequested(); Console.WriteLine("Task 2 - Int value {0}", i); } }, token2); // start all of the tasks task1.Start(); task2.Start(); // cancel the second token source tokenSource2.Cancel(); // write out the cancellation detail of each task Console.WriteLine("Task 1 cancelled? {0}", task1.IsCanceled); Console.WriteLine("Task 2 cancelled? {0}", task2.IsCanceled); // wait for input before exiting Console.WriteLine("Main method complete. Press enter to finish."); Console.ReadLine(); }