《CLR via C#》读书笔记-.NET多线程(四)
2016-11-07 21:23
323 查看
协作式取消
协作式取消其英文为: cooperative cancellation model。在26.4节中只是很简单的介绍了通过CancellationTokenSource来终结一个异步操作或长时间执行的同步操作。没有具体的分析和说明为什么要这样用。因为终结一个异步操作的方法有很多,可以使用最简单的
从.NET4开始,.NET Framework才为异步或需长时间执行的同步操作提供了协作取消模式。通常使用的有两个“东西“,一个是CancellationTokenSource,另一个是struct:CancellationToken。前者是取消请求的发起者,而后者是消息请求的监听者。就像量子世界中的量子纠缠一样,一个是根据现场的环境做出相应的响应,而另一个会立刻做出反应。CancellationTokenSource与CancellationToken就是这样的一个状态。
协作式取消的使用
协作式取消的使用步骤如下:
1、创建CancellationTokenSource实例
2、使用CancellationTokenSource实例的Token属性,获取CancellationToken,并将其传至Task或线程的相关方法中
3、在task或thread中提供根据CancellationToken.IsCancellationRequested属性值进行判定是否应该停止操作的机制
4、在程序中调用CancellationTokenSource实例的cancel方法
这儿有一篇文章,是使用CancellationTokenSource的具体例子。.Net 4.5中通过CancellationTokenSource实现对超时任务的取消
CancellationTokenSource
1、定义
CancellationTokenSource类的定义如下:
因本类实现了IDisposable的方法,因此在用完时需调用其dispose方法,或者是使用using
2、CancellationTokenSource与CancellationToken的关系
两者的关系如图所示:
![](http://img.blog.csdn.net/20161102213639736)
通过这张图,可得出:
1、不同的操作使用相同的CancellationTokenSource实例,就可以达到一次调用取消多个操作的目的。
2、CancellationToken为什么会是struct,而不是类
3、其他说明
1、除了CancellationTokenSource与CancellationToken之外,还有一个OperationCanceledException异常类,这个overload的异常类接受Token作为参数,因此在判断具体异常时,可使用本类
4、代码说明
代码如下:
以上方法使用的系统遗留方式,但是希望停止一个task时,参见如下:How to: Cancel a Task and Its Children
操作取消与对象取消(Operation Cancellation Versus Object Cancellation)
在协作式取消操作中,通常都是在方法中通过判断Token的IsCancellationRequested属性,然后根据这个属性的值对操作(或方法)进行相应的处理。因此,常用的协作式取消模式就是Operation Cancellation。PS.Token的IsCancellationRequested只能被设置一次,即当该属性被设置为true时,其不可能再被设为false,不能重复利用。另外,Token在被“用过”后,不能重复使用该对象。即,CancellationTokenSource对象只能使用一次,若希望重复使用,需要在每次使用时,创建新的对象。
除了操作取消之外,还有另外一种情况,我希望当CancellationTokenSource实例调用cancel方法时,调用某个实例中的某个方法。而这个方法内部没有CancellationToken对象。这个时候可以使用CancellationTokenSource的Register方法。
方法的定义如下:
其中Action是.NET内部的自定义的委托,其具体的定义:
可使用CancellationToken.Register方法完成对实例中方法的调用。如下有一个例子:
取消操作的监听与响应方式
在一般情况下,在方法内部使用使用Token.IsCancellationRequested属性判断其值,然后根据其值进行后续操作。这种模式可适应大部分的情况。但是有些情况需要额外的处理方式。
特别是当用户在使用一些外部的library代码时,上面提到的方式可能效果不好,更好的方法就是调用Token的方法 ThrowIfCancellationRequested(),让它抛出异常OperationCanceledException,外部的Library截住异常,然后通过判断异常的Token的相关属性值,再进行相应的处理。
ThrowIfCancellationRequested()的方法相当于:
因此在使用本方法时,通常的用法是(假设自己正在写的代码会被编译为Library,供其他人调用,则自己写的代码应该是这样的):
当别人使用Library时,需要在catch块中监听OperationCanceledException异常,代码如下:
以上是处理或写供别人使用的Library或DLL时应该遵循的方法。
在方法内部进行处理相关流程时,对于监听用户是否进行了取消操作,有如下的几种方式:
1.轮询式监听(Listening by Polling)
这种方法是最常用的,也是上面提到的,样例如下:
2.通过回调方法处理取消操作(Listening by Registering a Callback)
在比较复杂的情况下,可以使用register方法,注册或登记取消回调方法。如下所示:
在使用register方法时,有几个注意事项:
1、callback方法尽量要快!不要阻碍线程!因此Cancel方法要等到callback方法结束后才返回
2、callback方法要尽量不要再使用多线程。
3.多对象关联
可通过CancellationTokenSource的CreateLinkedTokenSource方法链接多个对象,从而形成一个新的CancellationTokenSource对象
链接中的任何一个对象使用了cancel方法,这个新的“链式”对象也会被取消。如下:
写在本节学习最后
1、若自己的程序需要封装为library,供其他人调用,则需要做好两点:1、方法需要接受一个token作为参数;2、需要较好的处理OperationCanceledException异常。
2、本节学习主要是结合:《CLR via C#》、MSDN的官网具体的网址在这儿, 以及网友的相关的文章。
协作式取消其英文为: cooperative cancellation model。在26.4节中只是很简单的介绍了通过CancellationTokenSource来终结一个异步操作或长时间执行的同步操作。没有具体的分析和说明为什么要这样用。因为终结一个异步操作的方法有很多,可以使用最简单的
true和
false变量结束异步操作。因此本次详细整理CLR的在线程取消的模式。本文参考了MSDN及其他网友的相关资料,具体的引用会在文章的尾端。
从.NET4开始,.NET Framework才为异步或需长时间执行的同步操作提供了协作取消模式。通常使用的有两个“东西“,一个是CancellationTokenSource,另一个是struct:CancellationToken。前者是取消请求的发起者,而后者是消息请求的监听者。就像量子世界中的量子纠缠一样,一个是根据现场的环境做出相应的响应,而另一个会立刻做出反应。CancellationTokenSource与CancellationToken就是这样的一个状态。
协作式取消的使用
协作式取消的使用步骤如下:
1、创建CancellationTokenSource实例
2、使用CancellationTokenSource实例的Token属性,获取CancellationToken,并将其传至Task或线程的相关方法中
3、在task或thread中提供根据CancellationToken.IsCancellationRequested属性值进行判定是否应该停止操作的机制
4、在程序中调用CancellationTokenSource实例的cancel方法
这儿有一篇文章,是使用CancellationTokenSource的具体例子。.Net 4.5中通过CancellationTokenSource实现对超时任务的取消
CancellationTokenSource
1、定义
CancellationTokenSource类的定义如下:
[ComVisibleAttribute(false)] [HostProtectionAttribute(SecurityAction.LinkDemand, Synchronization = true, ExternalThreading = true)] public class CancellationTokenSource : IDisposable
因本类实现了IDisposable的方法,因此在用完时需调用其dispose方法,或者是使用using
2、CancellationTokenSource与CancellationToken的关系
两者的关系如图所示:
通过这张图,可得出:
1、不同的操作使用相同的CancellationTokenSource实例,就可以达到一次调用取消多个操作的目的。
2、CancellationToken为什么会是struct,而不是类
3、其他说明
1、除了CancellationTokenSource与CancellationToken之外,还有一个OperationCanceledException异常类,这个overload的异常类接受Token作为参数,因此在判断具体异常时,可使用本类
4、代码说明
代码如下:
using System; using System.Threading; public class Example { public static void Main() { // Create the token source. CancellationTokenSource cts = new CancellationTokenSource(); // Pass the token to the cancelable operation. ThreadPool.QueueUserWorkItem(new WaitCallback(DoSomeWork), cts.Token); Thread.Sleep(2500); // Request cancellation. cts.Cancel(); Console.WriteLine("Cancellation set in token source..."); Thread.Sleep(2500); // Cancellation should have happened, so call Dispose. cts.Dispose(); } // Thread 2: The listener static void DoSomeWork(object obj) { CancellationToken token = (CancellationToken)obj; for (int i = 0; i < 100000; i++) { if (token.IsCancellationRequested) { Console.WriteLine("In iteration {0}, cancellation has been requested...", i + 1); // Perform cleanup if necessary. //... // Terminate the operation. break; } // Simulate some work. Thread.SpinWait(500000); } } } // The example displays output like the following: // Cancellation set in token source... // In iteration 1430, cancellation has been requested...
以上方法使用的系统遗留方式,但是希望停止一个task时,参见如下:How to: Cancel a Task and Its Children
操作取消与对象取消(Operation Cancellation Versus Object Cancellation)
在协作式取消操作中,通常都是在方法中通过判断Token的IsCancellationRequested属性,然后根据这个属性的值对操作(或方法)进行相应的处理。因此,常用的协作式取消模式就是Operation Cancellation。PS.Token的IsCancellationRequested只能被设置一次,即当该属性被设置为true时,其不可能再被设为false,不能重复利用。另外,Token在被“用过”后,不能重复使用该对象。即,CancellationTokenSource对象只能使用一次,若希望重复使用,需要在每次使用时,创建新的对象。
除了操作取消之外,还有另外一种情况,我希望当CancellationTokenSource实例调用cancel方法时,调用某个实例中的某个方法。而这个方法内部没有CancellationToken对象。这个时候可以使用CancellationTokenSource的Register方法。
方法的定义如下:
public CancellationTokenRegistration Register(Action callback)
其中Action是.NET内部的自定义的委托,其具体的定义:
public delegate void Action()
可使用CancellationToken.Register方法完成对实例中方法的调用。如下有一个例子:
using System; using System.Threading; class CancelableObject { public string id; public CancelableObject(string id) { this.id = id; } public void Cancel() { Console.WriteLine("Object {0} Cancel callback", id); // Perform object cancellation here. } } public class Example { public static void Main() { CancellationTokenSource cts = new CancellationTokenSource(); CancellationToken token = cts.Token; // User defined Class with its own method for cancellation var obj1 = new CancelableObject("1"); var obj2 = new CancelableObject("2"); var obj3 = new CancelableObject("3"); // Register the object's cancel method with the token's // cancellation request. token.Register(() => obj1.Cancel()); token.Register(() => obj2.Cancel()); token.Register(() => obj3.Cancel()); // Request cancellation on the token. cts.Cancel(); // Call Dispose when we're done with the CancellationTokenSource. cts.Dispose(); } } // The example displays the following output: // Object 3 Cancel callback // Object 2 Cancel callback // Object 1 Cancel callback
取消操作的监听与响应方式
在一般情况下,在方法内部使用使用Token.IsCancellationRequested属性判断其值,然后根据其值进行后续操作。这种模式可适应大部分的情况。但是有些情况需要额外的处理方式。
特别是当用户在使用一些外部的library代码时,上面提到的方式可能效果不好,更好的方法就是调用Token的方法 ThrowIfCancellationRequested(),让它抛出异常OperationCanceledException,外部的Library截住异常,然后通过判断异常的Token的相关属性值,再进行相应的处理。
ThrowIfCancellationRequested()的方法相当于:
if (token.IsCancellationRequested) throw new OperationCanceledException(token);
因此在使用本方法时,通常的用法是(假设自己正在写的代码会被编译为Library,供其他人调用,则自己写的代码应该是这样的):
if(!token.IsCancellationRequested) { //这儿正常的操作, //未被取消时,正常的代码和逻辑操作实现 }else { //代表用户进行了取消操作 //可以进行一些日志记录 //注销正在使用的资源 //然后就需要调用方法 token.ThrowIfCancellationRequested(); }
当别人使用Library时,需要在catch块中监听OperationCanceledException异常,代码如下:
try { //调用Library的方法 library.doSomethingMethod(); } catch(OperationCanceledException e1) { //捕获这个异常,代表是用户正常取消本操作,因此在这儿需要处理释放资源之类的事情 xxx.dispose(); } catch(exception e2) { //其他异常的具体处理方法 }
以上是处理或写供别人使用的Library或DLL时应该遵循的方法。
在方法内部进行处理相关流程时,对于监听用户是否进行了取消操作,有如下的几种方式:
1.轮询式监听(Listening by Polling)
这种方法是最常用的,也是上面提到的,样例如下:
static void NestedLoops(Rectangle rect, CancellationToken token) { for (int x = 0; x < rect.columns && !token.IsCancellationRequested; x++) { for (int y = 0; y < rect.rows; y++) { // Simulating work. Thread.SpinWait(5000); Console.Write("{0},{1} ", x, y); } // Assume that we know that the inner loop is very fast. // Therefore, checking once per row is sufficient. //就是下面的这句,通过for循环内部的轮询,去判断IsCancellationRequested属性值,从而去决定做其他的事情 if (token.IsCancellationRequested) { // Cleanup or undo here if necessary... Console.WriteLine("\r\nCancelling after row {0}.", x); Console.WriteLine("Press any key to exit."); // then... break; // ...or, if using Task: //若使用Task时,调用ThrowIfCancellationRequested方法,使其抛出异常 // token.ThrowIfCancellationRequested(); } } }
2.通过回调方法处理取消操作(Listening by Registering a Callback)
在比较复杂的情况下,可以使用register方法,注册或登记取消回调方法。如下所示:
using System; using System.Net; using System.Threading; using System.Threading.Tasks; class CancelWithCallback { static void Main() { var cts = new CancellationTokenSource(); var token = cts.Token; // Start cancelable task. // 这儿使用了一个Task,Task的使用和具体内容可参见多线程(五) Task t = Task.Run( () => { WebClient wc = new WebClient(); // Create an event handler to receive the result. wc.DownloadStringCompleted += (obj, e) => { // Check status of WebClient, not external token. if (!e.Cancelled) { Console.WriteLine("The download has completed:\n"); Console.WriteLine(e.Result + "\n\nPress any key."); } else { Console.WriteLine("The download was canceled."); } }; // Do not initiate download if the external token has already been canceled. // 当没有收到取消消息时,则进行相关的下载。 // 并且在初始化时,进行了回调方法的登记,因此,当token收到取消的方法时,则调用wc.CancelAsync() if (!token.IsCancellationRequested) { // Register the callback to a method that can unblock. using (CancellationTokenRegistration ctr = token.Register(() => wc.CancelAsync())) { Console.WriteLine("Starting request\n"); wc.DownloadStringAsync(new Uri("http://www.contoso.com")); } } }, token); Console.WriteLine("Press 'c' to cancel.\n"); char ch = Console.ReadKey().KeyChar; Console.WriteLine(); if (ch == 'c') cts.Cancel(); Console.WriteLine("Press any key to exit."); Console.ReadKey(); cts.Dispose(); } }
在使用register方法时,有几个注意事项:
1、callback方法尽量要快!不要阻碍线程!因此Cancel方法要等到callback方法结束后才返回
2、callback方法要尽量不要再使用多线程。
3.多对象关联
可通过CancellationTokenSource的CreateLinkedTokenSource方法链接多个对象,从而形成一个新的CancellationTokenSource对象
链接中的任何一个对象使用了cancel方法,这个新的“链式”对象也会被取消。如下:
var cts1=new CancellationTokenSource(); cts1.register(()=>Console.writeline("cts1被取消")); var cts2=new CancellationTokenSource(); cts2.register(()=>Console.writeline("cts2被取消")); var linkcts=CancellationTokenSource.CreateLinkedTokenSource(cts1,cts2); linkcts.register(()=>Console.writeline("LinkCts被取消")); cts2.cancel(); //其输出结果如下: //LinkCts被取消 //cts2被取消
写在本节学习最后
1、若自己的程序需要封装为library,供其他人调用,则需要做好两点:1、方法需要接受一个token作为参数;2、需要较好的处理OperationCanceledException异常。
2、本节学习主要是结合:《CLR via C#》、MSDN的官网具体的网址在这儿, 以及网友的相关的文章。
相关文章推荐
- 《CLR via C#》读书笔记-.NET多线程(二)
- 《CLR via C#》读书笔记-.NET多线程(六)
- 《CLR via C#》读书笔记-.NET多线程(三)
- 《CLR via C#》读书笔记-.NET多线程(五)
- 《CLR via C#》读书笔记-.NET多线程(一)
- CLR via C# 3 读书笔记(11):第2章 生成、打包、部署和管理应用程序与类型 — 2.3 元数据简介
- CLR via C# 3 读书笔记(13):第2章 生成、打包、部署和管理应用程序与类型 — 2.4 将模块组合为程序集(下)
- 【读书笔记】《框架设计(第2版)CLR Via C#》中两个比较有趣的知识点
- CLR via C# 3 读书笔记(14):第2章 生成、打包、部署和管理应用程序与类型 — 2.5 程序集版本资源信息
- CLR via C# 读书笔记(九)静态类,类型对象的生存周期
- 关于.NET(C#)中字符型(Char)与数字类型的转换, CLR via c# 读书笔记
- CLR via C# 3 读书笔记(5):第1章 CLR执行模型 — 1.5 本地代码生成器工具:NGen.exe
- CLR via C# 3 读书笔记(7):第1章 CLR执行模型 — 1.7 通用类型系统
- CLR via C# 读书笔记(三)关于"is" 和"as"操作符
- CLR via C# 读书笔记(二)new 操作符都干了些什么?
- CLR via C# 3 读书笔记(6):第1章 CLR执行模型 — 1.6 框架类库
- 《CLR via C#》第23章 执行异步调用 读书笔记 part1
- CLR via C# 3 读书笔记(2):第1章 CLR执行模型 — 1.2 将托管模块组合为程序集
- CLR via C# 读书笔记(一)关于C#代码的编译和执行
- CLR via C# 读书笔记(五)关于“out”和“ref”关键字