第二十七章 计算限制的异步操作
目录
27.1 CLR线程池基础
27.2 执行简单的计算限制操作
27.3 执行上下文
27.4 协作式取消和超时
27.5 任务
27.6 Parallel的静态For,ForEach和Invoke方法
27.7 并行语言集成查询(PLINQ)
27.8 执行定时计算限制操作
27.9 线程池如何管理线程
异步执行计算限制操作,允许线程池在多个CPU内核上调度任务,使多个线程能并发工作,从而高效率地使用系统资源,同时提升应用程序的吞吐能力。
27.1 CLR线程池基础
线程池是你的应用程序能使用的线程集合。每CLR一个线程池:这个线程池由CLR控制的所有AppDomain共享。
CLR初始化时,线程池中是没有线程的。在内部,线程池维护了一个操作请求队列。应用程序执行一个异步操作时,就调用某个方法,将一个记录项追加到线程池队列中。线程池的代码从这个队列中提取记录项,将这个记录项派发给一个线程池线程。
27.2 执行简单的计算限制操作
将一个异步的计算限制操作放到线程池队列中:ThreadPool类
static Boolean QueueUserWorkItem(WaitCallback callback);
static Boolean QueueUserWorkItem(WaitCallback callback, Object state);
27.3 执行上下文
每个线程都关联了一个执行上下文数据结构。执行上下文包括的东西有安全设置(压缩栈,Thread的Principal属性和Winodws身份)、宿主设置以及逻辑调用上下文数据。
ExecutionContext类:
[SecurityCritical] public static AsyncFlowControl SuppressFlow();
public static void RestoreFlow();
public static Boolean IsFlowSuppressed();
27.4 协作式取消和超时
Microsoft .NET Framework 提供了标准的取消操作模式。这个模式是协作式的,意味着要取消的操作必须显示支持取消。
System.Threading.CancellationTokenSoure包含了和管理取消有光的所有状态。
public struct CancellationToken:
CancellationToken 实例是轻量级值类型,包含单个私有字段,即对其CancellationToken的IsCancellationRequested属性,了解循环是否应该提前终止,从而终止计算限制的操作。
27.5 任务
System.Threading.Tasks命名空间
27.5.1 等待任务完成并获取结果
如果计算限制的任务抛出未处理的异常,异常会被“吞噬”并存储到一个集合中,而线程池线程可以返回到线程池中。调用Wait方法或者Result属性时,这些成员会抛出一个System.AggregateException对象
27.5.2 取消任务
可调用一个CancellationTokenSource取消Task。
27.5.3 任务完成时自动启动新任务
伸缩性好的软件不应该使线程阻塞
27.5.4 任务可以启动子任务
任务支持父/子关系。
27.5.5 任务内部揭秘
每个Task对象都有一组字段,这些字段构成了任务的状态。其中包括一个Int32ID,代表Task执行状态的一个Int32,对父任务的引用,对Task创建时指定的TaskScheduler的引用,对回调方法的引用,对要传给回调方法的对象的引用,对ExecutionContext的引用以及对MenualResetEventSlim对象的引用。另外,每个Task对象都有对根据需要创建的补充状态的引用。补充状态包含了一个CancellationToken,一个ContinueWithTask对象集合,为抛出未处理异常的子任务而准备的一个Task对象集合等。
首次构造Task对象时,它的状态时Created。以后,当任务启动时,它的状态变成WaitingToRun。Task实际在一个线程上运行时,它的状态变成Runing。任务停止运行,并等待它的任何子任务时,状态变成WaitingForChildrenToComplete。任务完成时进入以下状态之一:RanToCompletion(运行完成),Canceld(取消)或Faulted(出错)。
27.5.6 任务工厂
TaskFactory
27.5.7 任务调度器
TaskScheduler对象负责执行被调度的任务,同时向Visual Studio调试器公开任务信息。FCL提供了两个派生自TaskScheduler的类型:线程池任务调度器,和同步上下文任务调度器。任务调度器将任务调度给线程池的工作者线程。
27.6 Parallel的静态For,ForEach和Invoke方法
System.Threading.Tasks.Parallel:For,ForEach,Invoke
Parallel的所有方法都让调用线程参与处理。调用Parallel方法的前提是:工作项必须能并行执行。另外,要避免会修改任何共享数据的工作项,否则多个线程同时处理可能会损坏数据。
重载版本:
接受ParallelOptions对象:
CancellationToke:允许取消操作。
Int32MaxDegreeOfParallelism:允许指定可以并发操作的最大工作项目数。
TaskSchedulerTaskScheduler:允许指定要使用哪个TaskScheduler。
任务局部初始化委托:为参与工作的每个任务都调用一次该委托。这个委托是在任务别要求处理一个工作项之前调用的。
主体委托:为参与工作的各个线程所处理的每一项都调用一次该委托。
任务局部终结委托:为参与工作的每一个任务都调用一次该委托。这个委托是在任务处理好派发给它的所有工作项之后调用。即使主体委托代码引发一个未处理的异常,也会调用它。
27.7 并行语言集成查询(PLINQ)
使用LINQ to Objects时,只有一个线程顺序处理数据集合中的所有项:我们称之为顺序查询。要提高处理性能,可以使用并行LINQ(parallel LINQ),它将顺序查询转换成并行查询,在内部使用任务,将集合中的数据项的处理工作分散到多个CPU上,以便并发处理多个数据项。
AsParallel;AsSequential
27.8 执行定时计算限制操作
System.Threading命名空间定义了一个Timer类,可用它让一个线程池线程定时调用一个方法。
System.Threading的Timer类:要在一个线程池线程上执行定时的(周期性发生的)后台任务,它是最好的计时器。
System.Windows.Forms的Timer类:构造这个类的实例,相当于告诉Windows将一个计时器和调用线程关联。
System.Windows.Threading的DispatcherTimer类:这个类是System.Windows.Forms的Timer类在Silverlight和WPF应用程序中的等价物。
Windows.UI.Xaml的DispatcherTimer类:这个类是System.Windows.Forms的Timer类在Windows Store应用中的等价物。
System.Timers的Timer类:这个计时器本质上是System.Threading的Timer类包装器。计时器到期(触发)会导致CLR将事件放到线程池队列中。(不建议使用)
27.9 线程池如何管理线程
27.9.1 设置线程池限制
CLR允许开发人员设置线程池要创建的最大线程数。但实践证明,线程池永远都不应该设置线程数上限,因为可能发生饥饿或死锁。
27.9.2 如何管理工作者线程
ThreadPool.QueueUserWorkItem方法和TImer类总是将工作项放到全局队列中。工作者线程采用一个先入先出(FIFO)算法将工作项从这个队列中取出,并处理它们。由于多个工作者线程可能同时从全局队列中拿走工作项,所以所有工作者线程都竞争一个线程同步锁,以保证两个或多个线程不会获取同一个工作项。
每个工作者线程都有自己的本地队列。工作者线程调度一个Task时,该Task被添加到调用线程的本地队列。非工作者线程调度一个Task时,该Task被添加到全局队列。工作者线程采用后入先出(LIFO)算法将任务从本地队列中取出。由于工作者线程是唯一允许访问它自己的本地队列头的线程,所以无需同步锁,而且在队列中添加和删除Task的速度非常快,