《CLR via C#》读书笔记 之 计算限制的异步操作

2014-07-06

26.1 CLR线程池基础


返回

如25章所述,创建和销毁线程是一个比较昂贵的操作:

  • 太多的线程也会浪费内存资源。
  • 由于操作系统必须调度可运行的线程并执行上下文切换,所以太多的线程还有损于性能。

为了改善这个情况,CLR使用了代码来管理它自己的线程池。可将线程池想像成可由你的应用程序使用的一个线程集合。每个进程都有一个线程池,它在各个应用程序域(AppDomain)是共享的.

线程池是如何工作的:

  • CLR初始化时,线程池是没有线程的。在内部,线程池维护了一个操作请求队列。应用程序想执行一个异步操作时,就调用某个方法,将一个记录项(entry)追加到线程池的队列中。线程池的代码从这个队列中提取记录项,将这个记录项派遣(dispatch)给一个线程池线程。如果线程池中没有线程,就创建新的线程。创建线程要产生一定的性能损失。然而,当线程池完成任务后,线程不会被销毁。相反,线程会返回线程池,在那里进入空闲状态,等待响应另一个请求。由于线程不销毁自身,所以不再产生额外的性能损失。
  • 如果你的应用程序向线程池发出许多请求,线程池会尝试只用一个线程来服务所有的请求。然而,如果你的应用程序发出请求的速度超过了线程池处理它们的速度,就会创建额外的线程。最终,你的应用程序所有请求都可能有少量的线程处理,所有线程池不必创建大量的线程。
  • 如果你的应用程序停止向线程池发出请求,池中含有大量空闲的线程。这是对内存资源的一种浪费。所以,当一个线程池线程空闲一段时间以后,线程会自己醒来终止自己以释放资源。

在内部,线程池将自己的线程划分为工作者(Worker)线程和I/O线程。应用程序要求线程池执行一个异步的计算限制操作时(这个操作可能发起一个I/O限制的操作),使用的就是工作者线程。I/O线程用于通知你的代码一个异步I/O限制操作已经完成,具体的说,这意味着使用"异步编程模型"发出I/O请求,比如访问文件、网络服务器、数据库等等。

26.2 执行简单的计算限制操作


返回

将一个异步的、计算限制的操作放到一个线程池的队列中,通常可以调用ThreadPool类定义的以下方法之一:

1 static Boolean QueueUserWorkItem(WaitCallback callBack);
2 static Boolean QueueUserWorkItem(WaitCallback callBack,Object state);

这些方法向线程池的队列中添加一个"工作项"(work item)以及可选的状态数据, 如果此方法成功排队,则为 true;如果无法将该工作项排队,则引发 OutOfMemoryException。工作项其实就是由callBack参数标识的一个方法,该方法将由线程池线程调用。可通过state实参(状态数据)向方法传递一个参数。无state参数的那个版本的QueueUserWorkItem则向回调方法传递null。最终,池中的某个线程会处理工作项,造成你指定的方法被调用。你写的回调方法必须匹配System.Threading.WaitCallBack委托类型,它的定义如下:

delegate void WaitCallback(Object state);

注意:WaitCallback委托、TimerCallback委托(在本章26.8节“执行时计算限制操作”中讨论)和ParameterizedThreadStart委托(在第25章“线程基础”中讨论)是完全一致的。因此,只要定义一个和该签名匹配的方法后,使用ThreadPool.QueueUserWorkItem方法,使用System.Threading.Timer对象,或着使用System.Threading.Thread对象,都一个调用这个方法。

以下演示了如何让一个线程池线程以异步方式调用一个方法:

 1 using System;
 2 using System.Threading;
 3     class Program
 4     {
 5         static void Main(string[] args)
 6         {
 7             Console.WriteLine("Main thread: queuing an asynchronous operation");
 8             ThreadPool.QueueUserWorkItem(ComputeBoundOp, 5);
 9             Console.WriteLine("Main thread: Doing other work here...");
10             Thread.Sleep(10000);  // 模拟其它工作 (10 秒钟)
11             //Console.ReadLine();
12         }
13
14         // 这是一个回调方法,必须和WaitCallBack委托签名一致
15         private static void ComputeBoundOp(Object state)
16         {
17             // 这个方法通过线程池中线程执行
18             Console.WriteLine("In ComputeBoundOp: state={0}", state);
19             Thread.Sleep(1000);  // 模拟其它工作 (1 秒钟)
20
21             // 这个方法返回后,线程回到线程池,等待其他任务
22         }
23     }

View Code

编译运行的结果是:
  Main thread: queuing an asynchronous operation
  Main thread: Doing other work here...
  In ComputeBoundOp: state=5
但有时也会得到一下输出:
  Main thread: queuing an asynchronous operation
  In ComputeBoundOp: state=5
  Main thread: Doing other work here...
之所以有两种输出结果,是因为这两个方法相互之间是异步运行的。由Windows调度器决定先调度哪一个线程。

26.3 执行上下文


返回

每个线程都关联了一个执行上下文数据结构。执行上下文(execution context)包括的东西有:

  • 安全设置:压缩栈、Thread的Principal属性[指示线程的调度优先级]和Windows身份;
  • 宿主设置:参见System.Threading.HostExecutionContextManager[提供使公共语言运行时宿主可以参与执行上下文的流动(或移植)的功能];
  • 逻辑调用上下文数据:参见System.Runtime.Remoting.Messaging.CallContext[提供与执行代码路径一起传送的属性集]的LogicalSetData[将一个给定对象存储在逻辑调用上下文中并将该对象与指定名称相关联]和LogicalGetData[从逻辑调用上下文中检索具有指定名称的对象]。

线程执行代码时,有的操作会受到线程的执行上下文设置(尤其是安全设置)的影响。理想情况下,每当一个线程(初始线程)使用另一个线程(辅助线程)执行任务时,前者的执行上下文应该"流动"(复制)到辅助线程。这就确保辅助线程执行的任何操作使用的都是相同的安全设置和宿主设置。还确保了初始线程的逻辑调用上下文可以在辅助线程中使用。

默认情况下,CLR自动造成初始线程的执行上下文会"流动"(复制)到任何辅助线程。这就是将上下文信息传输到辅助线程,但这对损失性能,因为执行上下文中包含大量信息,而收集这些信息,再将这些信息复制到辅助线程,要耗费不少时间。如果辅助线程又采用更多的辅助线程,还必须创建和初始化更多的执行上下文数据结构。

System.Threading命名空间中有一个ExecutionContext类[管理当前线程的执行上下文],它允许你控制线程的执行上下文如何从一个线程"流动"(复制)到另一个线程。下面展示了这个类的样子:

 1   public sealed class ExecutionContext : IDisposable, ISerializable
 2   {
 3     [SecurityCritical]
 4     //取消执行上下文在异步线程之间的流动
 5     public static AsyncFlowControl SuppressFlow();
 6     //恢复执行上下文在异步线程之间的流动
 7     public static void RestoreFlow();
 8     //指示当前是否取消了执行上下文的流动。
 9     public static bool IsFlowSuppressed();
10
11     //不常用方法没有列出
12   }

View Code

可用这个类阻止一个执行上下文的流动,从而提升应用程序的性能。对于服务器应用程序,性能的提升可能非常显著。但是,客户端应用程序的性能提升不了多少。另外,由于SuppressFlow方法用[SecurityCritical]attribute进行了标识,所以在某些客户端应用程序(比如Silverlight)中是无法调用的。当然,只有在辅助线程不需要或者不防问上下文信息时,才应该组织执行上下文的流动。如果初始线程的执行上下文不流向辅助线程,辅助线程会使用和它关联起来的任何执行上下文。在这种情况下,辅助线程不应该执行要依赖于执行上下文状态(比如用户的Windows身份)的代码。

注意:添加到逻辑调用上下文的项必须是可序列化的。对于包含了逻辑调用上下文数据线的一个执行上下文,如果让它流动,可能严重损害性能,因为为了捕捉执行上下文,需对所有数据项进行序列化和反序列化。

下例展示了向CLR的线程池队列添加一个工作项的时候,如何通过阻止执行上下文的流动来影响线程逻辑调用上下文中的数据:

 1         static void Main(string[] args)
 2         {
 3             // 将一些数据放到Main线程的逻辑调用上下文中
 4             CallContext.LogicalSetData("Name", "Jeffrey");
 5
 6             // 线程池能访问到逻辑调用上下文数据,加入到程序池队列中
 7             ThreadPool.QueueUserWorkItem(
 8                state => Console.WriteLine("Name={0}", CallContext.LogicalGetData("Name")));
 9
10
11             // 现在阻止Main线程的执行上下文流动
12             ExecutionContext.SuppressFlow();
13
14             //再次访问逻辑调用上下文的数据
15             ThreadPool.QueueUserWorkItem(
16                state => Console.WriteLine("Name={0}", CallContext.LogicalGetData("Name")));
17
18             //恢复Main线程的执行上下文流动
19             ExecutionContext.RestoreFlow();
20         }

View Code

会得到一下结果:
  Name=Jeffrey
  Name=

虽然现在我们讨论的是调用ThreadPool.QueueUserWorkItem时阻止执行上下文的流动,但在使用Task对象(参见26.5节”任务“),以及在发起异步I/O操作(参见第27章“I/o限制的异步操作”)时,这个技术也会用到。

参考

[1] [CLR via C#]26. 计算限制的异步操作

转载于:https://www.cnblogs.com/Ming8006/p/3828480.html

《CLR via C#》读书笔记 之 计算限制的异步操作相关推荐

  1. CLR Via CSharp读书笔记(26) - 计算限制的异步操作

    执行上下文: 执行上下文包括安全设置(压缩栈.Thread的Principal属性和Windows身份), 宿主设置(System.Threading.HostExecutionContextMana ...

  2. 【读书笔记】计算广告(第1部分)

    作者:LogM 本文原载于 https://segmentfault.com/u/logm/articles,不允许转载~ 本文是 计算广告(第二版) 的读书笔记. 该部分介绍一些基本知识. 第1章 ...

  3. 【读书笔记】计算广告学 Mooc

    作者:LogM 本文原载于 https://segmentfault.com/u/logm/articles,不允许转载~ 本文是 计算广告学Mooc 的读书笔记,Mooc讲得比较宏观,没涉及太多具体 ...

  4. CLR via C# 读书笔记 1-2 创建线程的成本

    在clr中创建线程的代价还是比较高的 ,他需要两个部分 内存: 线程核心对象, 存放描述线程的一些内容和上下文 . (内存消耗:700B-2500B) 线程环境,存放例如异常处理链之类. (内存消耗 ...

  5. CLR Via CSharp读书笔记(14):字符、字符串和文本处理

    数字类型与字符相互转换的三种技术: Casting: 最优效率,因为编译器产生IL指令来执行转换.C#允许开发者指明checked或unchecked代码以执行转换. 使用System.Convert ...

  6. CLR via C# 读书笔记 5-5 预留大内存

    在进行需要很大内存空间的计算时,非常容易发生OutOfMemoryException System.Runtime.MemoryFailPoint提供了在运行需要大内存的计算前进行内存检查的功能 在调 ...

  7. 读书笔记-Icepak计算收敛标准

    残差曲线 各变量方程(连续性.动量.能量方程)残差达到默认残差标准: Flow的残差标准为0.001(1e-3). 能量方程残差Energy为1e-7. 外太空散热,仅计算热传导和辐射换热,能量方程E ...

  8. 读书笔记《计算广告》

    目录: 第一部分 在线广告市场与背景 第二部分 在线广告产品逻辑 第三部分 计算广告关键技术 一.在线广告市场与背景 在线广告综述 计算广告算是大数据应用中最为成熟,市场规模最大的行业 对于数据问题的 ...

  9. CLR Via CSharp读书笔记(29) - 混合线程同步构造

    {TODO:} 转载于:https://www.cnblogs.com/thlzhf/p/3494561.html

  10. CLR Via CSharp读书笔记(7):常量和字段

    {TODO:} 转载于:https://www.cnblogs.com/thlzhf/archive/2012/12/06/2805424.html

最新文章

  1. 深入浅出下一代互联网基础IPFS
  2. zookeeper zoo.cfg配置文件
  3. html表格通过邮件发送,通过html表格发电子邮件
  4. 空中交警:借你一双“慧眼”,让你看透这飞机的“黑色十分钟”
  5. [转] 在 Mac OS X 下编译 Objective-C 运行时
  6. java安装选择哪个可选功能_java章节习题及期末考试题答案.doc
  7. python json是什么_python的json用法
  8. JavaScript-常用正则函数(适合忘记时看)
  9. Java Queue 使用总结
  10. FailSafe双机方案
  11. 字符串当id用 转换成json对象
  12. ‍炮灰模型---------- 对女生选择追求者的数学模型的建立
  13. 操作系统设备驱动实验实验报告
  14. 推荐一个简洁免费轻量级的思维导向图软件Blumind
  15. Stata:何时使用线性概率模型而非Logit?
  16. SpringMVC工作原理概述
  17. IPv6技术精要--第5章 IPv6公网单播地址
  18. 2017年语义理解总结(一)
  19. Windows10 LSTC 2021输入法无法使用的问题
  20. 用java判断三角形类型_判断三角形类型

热门文章

  1. java打包-exe文件-最终以setup形式发布的解决之道
  2. linux内核之dmaengine
  3. Hi3520d 网卡驱动源码分析
  4. 使用BCC工具获取Linux内核空间read/write操作的文件名
  5. Linux进程调度技术的前世今生
  6. Snmp4j编程简介之一
  7. H264的视频格式H264支持4:2:0的连续或隔行视频的编码和解码
  8. ARM汇编指令MCR/MRC学习
  9. scala中sorted,sortWith,sortBy用法详解
  10. 统计某个字符串出现的次数