本节书摘来自华章出版社《C#多线程编程实战(原书第2版)》一书中的第3章,第3.1节,作者(美)易格恩·阿格佛温(Eugene Agafonov),黄博文 黄辉兰 译,更多章节内容可以访问云栖社区“华章计算机”公众号查看。
第3章
使用线程池
在本章中,我们将描述多线程中使用共享资源的常用技术。你将学到以下内容:
在线程池中调用委托
向线程池中放入异步操作
线程池与并行度
实现一个取消选项
在线程池中使用等待事件处理器及超时
使用计时器
使用BackgroundWorker组件
3.1简介
在之前的章节中我们讨论了创建线程和线程协作的几种方式。现在考虑另一种情况,即只花费极少的时间来完成创建很多异步操作。正如在第1章的简介小节中讨论过的一样,创建线程是昂贵的操作,所以为每个短暂的异步操作创建线程会产生显著的开销。
为了解决该问题,有一个常用的方式叫作池(pooling)。线程池可以成功地适应于任何需要大量短暂的开销大的资源的情形。我们事先分配一定的资源,将这些资源放入到资源池。每次需要新的资源,只需从池中获取一个,而不用创建一个新的。当该资源不再被使用时,就将其返回到池中。
.NET线程池是该概念的一种实现。通过System.Threading.ThreadPool类型可以使用线程池。线程池是受.NET通用语言运行时(Common Language Runtime,简称CLR)管理的。这意味着每个CLR都有一个线程池实例。ThreadPool类型拥有一个QueueUserWorkItem静态方法。该静态方法接受一个委托,代表用户自定义的一个异步操作。在该方法被调用后,委托会进入到内部队列中。如果池中没有任何线程,将创建一个新的工作者线程(worker thread)并将队列中第一个委托放入到该工作者线程中。
如果想线程池中放入新的操作,当之前的所有操作完成后,很可能只需重用一个线程来执行这些新的操作。然而,如果放置新的操作过快,线程池将创建更多的线程来执行这些操作。创建太多的线程是有限制的,在这种情况下新的操作将在队列中等待直到线程池中的工作者线程有能力来执行它们。
保持线程中的操作都是短暂的是非常重要的。不要在线程池中放入长时间运行的操作,或者阻塞工作者线程。这将导致所有工作者线程变得繁忙,从而无法服务用户操作。这会导致性能问题和非常难以调试的错误。
当停止向线程池中放置新操作时,线程池最终会删除一定时间后过期的不再使用的线程。这将释放所有那些不再需要的系统资源。
我想再次强调线程池的用途是执行运行时间短的操作。使用线程池可以减少并行度耗费及节省操作系统资源。我们只使用较少的线程,但是以比平常更慢的速度来执行异步操作,使用一定数量的可用的工作者线程批量处理这些操作。如果操作能快速地完成则比较适用线程池,但是执行长时间运行的计算密集型操作则会降低性能。
另一个重要事情是在应用程序中使用线程池时要相当小心。基础设施使用自己的线程池,如果在线程池中浪费所有的工作者线程,Web服务器将不能够服务新的请求。在中只推荐使用输入/输出密集型的异步操作,因为其使用了一个不同的方式,叫做I/O线程。我们将在第9章中讨论I/O线程。
请注意线程池中的工作者线程都是后台线程。这意味着当所有的前台线程(包括主程序线程)完成后,所有的后台线程将停止工作。
在本章中,我们将学习使用线程池来执行异步操作。本章将覆盖将操作放入线程池的不同方式,以及如何取消一个操作,并防止其长时间运行。