读书笔记—CLR via C#线程27章节

前言

这本书这几年零零散散读过两三遍了,作为经典书籍,应该重复读反复读,既然我现在开始写博了,我也准备把以前觉得经典的好书重读细读一遍,并且将笔记整理到博客中,好记性不如烂笔头,同时也在写的过程中也可以加深自己理解的深度,当然同时也和技术社区的朋友们共享

同步IO执行过程,拿Read举例

  • 托管代码转变为本地用户模式代码,Read在内部调用Win32的ReadFile函数
  • ReadFile分配IRP(IO Request Packet)
  • IRP包括:一个文件句柄、文件偏移量、Byte[]数组
  • IO请求进入Windows内核模式,传递IRP,调用内核,根据设备句柄,内核将IRP分发给设备驱动的IRP队列
  • 线程在IRP队列里阻塞,硬件执行IO,不涉及到任何线程
  • 线程虽然变成睡眠,节省了CPU时间,但是依然浪费了空间(用户模式栈、内核模式栈、TEB等)
  • 硬件设备完成IO,Windows唤醒线程,把它调度给一个CPU
  • 线程从内核模式返回用户模式,再返回托管代码

同步IO的危害

  • 降低服务器响应能力和吞吐量
  • 浪费过多的系统资源(线程和内存)
  • 频繁的上下文切换
  • 线程池频繁创建更多的新线程、阻塞线程醒来时又是上下文切换

异步IO

异步IO的过程的区别在于,IRP请求在内核模式添加到硬盘驱动程序的IRP队列之后,线程不再阻塞,而是允许返回代码,线程立即返回。对回调方法调用的委托实际会在IRP中一路传递道设备驱动程序。硬件处理好IRP后,将IRP的委托放到CLR的线程池队列中。线程池线程提取完成的IRP,并调用回调方法。调用BeginXXX方法时,它构造一个对象来唯一标识IO请求,将请求加入Windows设备驱动程序队列,然后返回对IAsyncResult的引用。在内部CLR线程池使用IOCP来完成异步请求的绑定和发送

异步IO的好处

  • 降低资源使用率
  • 减少上下文切换
  • 提升GC性能和调试性能
  • 提高并发量和吞吐量

APM

某一些提供Begin和End方法接口的类,但不与硬件设备通信,这些方法的代码仅仅执行计算限制的操作。不能执行IO限制的操作,因此需要一个线程来执行这些操作

FLC中还有其他:文件流类、网络流类、数据库类、Webservice、WCF

基于传统的begin...,end...模式的异步实现,如果该实现是基于IOCP的话,那么它并不会阻塞在IO线程上。它的实现原理是将IO请求转换成IRP(IO Request Package)然后传递到硬件设备驱动的IRP Queue中,调用begin..方法的线程会立马返回,然后硬件驱动会去它的IRP Queue取出工作项执行,真正执行的时候也不会用到任何线程。当实现完之后会把irp逐层向上抛直到IOCP,然后会将其扔给threadpool,threadpool会选用一个IO线程执行begin...方法中传递的回调方法

委托的BeginInvoke方法在内部调用ThreadPool.QueueUserWorkItem将计算限制操作添加道CLR的线程池队列。最好将IAsyncResult返回调用者。
如果存在回调,执行完后会线程池线程不会回到池中,会调用回调

不建议:

  • 直接调用End,会阻塞
  • 要避免使用IAsyncResult的WaitHandle属性,会阻塞线程,可能造成线程池分配另一个线程
  • 查询IsCompleted,也不建议,会浪费CPU事件

建议

  • 总是调用End,而且只调用一次。(1.释放资源 2. 处理异常)
  • 调用End方法时应该和Begin方法相同的对象

异常

调用Begin抛异常时,表示异步操作没有进入队列,所以线程池线程不会调用传给Begin的任何回调方法。设备驱动程序向CLR线程池post已完成的IRP,并会在代表异步操作的IAsyncResult中放入一个错误码。线程池调用回调方法,传递Result,回调方法把Result传给恰当的End方法,End方法发现错误码会把它转换成恰当的Exception异常

一般关心从End方法调用抛出的异常

如果采用委托的方式异步调用某个没有返回值的方法, 那么,当你不调用EndInvoke时,你是不知道是否有异常抛出的

委托异步

委托的异步调用是将任务交给线程池的工作线程来执行的

对于delegate的begininvoke的异步实现是在threadpool里的线程来运行的,但是它是基于remoting的架构实现的。这点明显很费性能,所以最好不要用它

线程上下文切换

Context.Post方法将回调送到GUI线程队列中,允许线程池线程立即返回,Send也将回调送入GUI线程队列,但随后会阻塞线程池线程,但随后会阻塞线程池线程,知道GUI线程完成对回调方法的调用。注意,这些Context都在内部调用BeginInvoke(Post)或Invoke方法。EAP中还是用了AsyncOperationManager

EAP

用起来比较方便,但是它是由wiondows form团队开发的,主要是提供给winForm使用的,能很好地在winform开发中使用,但是它会在每次出发时产生EventArgs对象,会造成很多垃圾。其次,使用时间地方式来通知外面也是有性能损失的,对于delegate的调用比virtual方法地调用性能还要差

EAP的诸多限制,同时实现两个模式。支持EAP的类自动将应用程序模型映射到它的线程处理模型。在内部使用了SynchronizationContext类

backgroundworker这个组件本质还是通过delegate的begininvoke来做的(可以通过reflector来查看),所以也是不推荐使用的

BackgroundWorker用于执行异步的计算限制的工作。不用于执行IO限制的工作

EAP相较APM的缺点,1:包装损耗 2:实际订阅容易内存释放 3.错误处理不一致

APM与Task的转换

  • IAsyncResult APM转换为Task,通过Task.Factory.FromAsync<Response>。ask实现了IAsyncResult接口,可以兼容APM。Task封装了对End方法的调用
  • 将EAP转变为Task,在EAP的事件回调中针对TaskCompletionSource进行创建

.NET Framework 的异步编程模型

结语

我知道,Jeffrey Ritchter在线程章节花了很大的心血,但是书写时还是没有很好的归纳清楚,比如他用25章讲线程基础和线程的发展和历史背景,这OK无可厚非,但是26章和27章明显就有点没有章法了,他在26章的标题是计算限制的异步,讲线程池然后直接跳到TPL的部分(顺带讲了一下定时器),27章标题是IO限制的异步,可是实际上在讲APM和EAP。哎,所以导致我看书笔记也写的很凌乱,勿怪,仅仅作为自己的一个记录,罢了!

时间: 2024-12-27 21:52:24

读书笔记—CLR via C#线程27章节的相关文章

读书笔记—CLR via C#线程25-26章节

前言 这本书这几年零零散散读过两三遍了,作为经典书籍,应该重复读反复读,既然我现在开始写博了,我也准备把以前觉得经典的好书重读细读一遍,并且将笔记整理到博客中,好记性不如烂笔头,同时也在写的过程中也可以加深自己理解的深度,当然同时也和技术社区的朋友们共享 线程 线程内部组成 线程内核对象 thread kernel object,在该结构中,包含一组对线程进行描述的属性.数据结构中还包括所谓的线程上下文thread context.上下文是一个内存块,包含了CPU的寄存器集合,占用几百到几千个字

读书笔记—CLR via C#同步构造28-29章节

前言 这本书这几年零零散散读过两三遍了,作为经典书籍,应该重复读反复读,既然我现在开始写博了,我也准备把以前觉得经典的好书重读细读一遍,并且将笔记整理到博客中,好记性不如烂笔头,同时也在写的过程中也可以加深自己理解的深度,当然同时也和技术社区的朋友们共享 类库和线程安全 在类设计中,类和方法的线程安全的设计尽量和FCL保持一致 保证所有的静态方法都是线程安全的 不保证实例方法是线程安全的 基元用户模式和基元内核模式构造 用户模式构造 易失构造(volatile construct),它包含一个简

读书笔记—CLR via C#章节11-13

前言 这本书这几年零零散散读过两三遍了,作为经典书籍,应该重复读反复读,既然我现在开始写博了,我也准备把以前觉得经典的好书重读细读一遍,并且将笔记整理到博客中,好记性不如烂笔头,同时也在写的过程中也可以加深自己理解的深度,当然同时也和技术社区的朋友们共享 事件 事件的本质 初始化为null的私有委托字段 封装add_Event和remove_Event方法 add_Event和remove_Event的可访问性同Event字段的可访问性,包括virtual和static修饰 事件的编码建议 使用

读书笔记—CLR via C#章节8-10

前言 这本书这几年零零散散读过两三遍了,作为经典书籍,应该重复读反复读,既然我现在开始写博了,我也准备把以前觉得经典的好书重读细读一遍,并且将笔记整理到博客中,好记性不如烂笔头,同时也在写的过程中也可以加深自己理解的深度,当然同时也和技术社区的朋友们共享 构造函数 抽象类默认的构造函数可访问为protected,普通类默认为public 派生类默认调用一个基类构造器(如果没有无参构造器,则需要显式调用) 不通过构造函数创建对象? 静态方法Object.MemberwiseClone,分配内存复制

读书笔记—CLR via C#章节3

这本书这几年零零散散读过两三遍了,作为经典书籍,应该重复读反复读,既然我现在开始写博了,我也准备把以前觉得经典的好书重读细读一遍,并且将笔记整理到博客中,好记性不如烂笔头,同时也在写的过程中也可以加深自己理解的深度,当然同时也和技术社区的朋友们共享. 强命名程序集 优点 程序集共享 版本共存,解决DLL hell 安全策略(防篡改) 发布策略控制 标识组成 文件名(不计扩展名)+版本号+语言文化+公钥 CLR加载方式 弱命名程序集私有部署,CLR在基目录或子目录中搜索时只使用程序集名称 CLR搜

读书笔记—CLR via C#章节4-7

前言 这本书这几年零零散散读过两三遍了,作为经典书籍,应该重复读反复读,既然我现在开始写博了,我也准备把以前觉得经典的好书重读细读一遍,并且将笔记整理到博客中,好记性不如烂笔头,同时也在写的过程中也可以加深自己理解的深度,当然同时也和技术社区的朋友们共享 对象的祖先 – Object 公共方法 Equals, 虚方法,对象相等性,默认调用RuntimeHelpers.Equals方法 GetHashCode,虚方法,返回哈希吗(随机分布的整数),在哈希表中作为键使用 ToString , 默认返

读书笔记—CLR via C#章节1-2

这本书这几年零零散散读过两三遍了,作为经典书籍,应该重复读反复读,既然我现在开始写博了,我也准备把以前觉得经典的好书重读细读一遍,并且将笔记整理到博客中,好记性不如烂笔头,同时也在写的过程中也可以加深自己理解的深度,当然同时也和技术社区的朋友们共享. 程序集 描述:一个或多个类型定义文件及资源文件的集合 特征:可重用.可保护.可版本控制的单元 生成:可通过C#编译器(或其他编译器)或AL.exe生成 组成: 托管模块(module) PE头,PE32或PE32+,面向CPU架构的信息 CLR头,

读书笔记—CLR via C#异常和状态管理

前言 这本书这几年零零散散读过两三遍了,作为经典书籍,应该重复读反复读,既然我现在开始写博了,我也准备把以前觉得经典的好书重读细读一遍,并且将笔记整理到博客中,好记性不如烂笔头,同时也在写的过程中也可以加深自己理解的深度,当然同时也和技术社区的朋友们共享 Tips vs调试catch块时,监视窗口变量: $exception 查看当前抛出的异常对象 异常的catch是自上而下,回溯调用栈,如果未找到,就抛出未处理异常 异常的执行顺序:先执行body,再执行catch,最后执行finally 堆栈

读书笔记—CLR via C#反射

前言 这本书这几年零零散散读过两三遍了,作为经典书籍,应该重复读反复读,既然我现在开始写博了,我也准备把以前觉得经典的好书重读细读一遍,并且将笔记整理到博客中,好记性不如烂笔头,同时也在写的过程中也可以加深自己理解的深度,当然同时也和技术社区的朋友们共享 程序集加载 AppDomain.Load 尽量避免使用此方法加载程序集,因为Assembly不是从System.MarshalByRefObject派生,所以程序集对象必须按值封送回发出调用的那个AppDomain,但是CLR会使用发出调用的那