本节书摘来自华章出版社《C#多线程编程实战(原书第2版)》一书中的第3章,第3.2节,作者(美)易格恩·阿格佛温(Eugene Agafonov),黄博文 黄辉兰 译,更多章节内容可以访问云栖社区“华章计算机”公众号查看。
3.2 在线程池中调用委托
本节将展示在线程池中如何异步的执行委托。另外,我们将讨论一个叫做异步编程模型(Asynchronous Programming Model,简称APM)的方式,这是.NET历史中第一个异步编程模式。
3.2.1 准备工作
为了学习本节,你需要安装Visual Studio 2015。除此之外无需其他准备。本节的源代码放置在BookSamplesChapter3Recipe1目录中。
3.2.2 实现方式
请执行以下步骤来了解如何在线程池中调用委托:
1.启动Visual Studio 2015。新建一个C#控制台应用程序项目。
2.在Program.cs文件中加入以下using指令:
3.在Main方法下面加入以下代码片段:
4.在Main方法中加入以下代码片段:
5.运行程序。
3.2.3 工作原理
当程序运行时,使用旧的方式创建了一个线程,然后启动它并等待完成。由于线程的构造函数只接受一个无任何返回结果的方法,我们使用了lambda表达式来将对Test方法的调用包起来。我们通过打印出Thread. CurrentThread.IsThreadPoolThread属性值来确保该线程不是来自线程池。我们也打印出了受管理的线程ID来识别代码是被哪个线程执行的。
然后定义了一个委托并调用BeginInvoke方法来运行该委托。BeginInvoke方法接受一个回调函数。该回调函数在异步操作完成后会被调用,并且一个用户自定义的状态会传给该回调函数。该状态通常用于区分异步调用。结果,我们得到一个实现了IAsyncResult接口的result对象。BeginInvoke立即返回了结果,当线程池中的工作者线程在执行异步操作时,仍允许我们继续其他工作。当需要异步操作的结果时,可以使用BeginInvoke方法调用返回的result对象。我们可以使用result对象的IsCompleted属性轮询结果。但是在本例子中,使用的是AsyncWaitHandle属性来等待直到操作完成。当操作完成后,会得到一个结果,可以通过委托调用EndInvoke方法,传递委托参数和IAsyncResult对象。
事实上使用AsyncWaitHandle并不是必要的。如果注释掉r.AsyncWaitHandle.WaitOne,代码照样可以成功运行,因为EndInvoke方法事实上会等待异步操作完成。调用EndInvoke方法(或者针对其他异步API的EndOperationName方法)是非常重要的,因为该方法会将任何未处理的异常抛回到调用线程中。当使用这种异步API时,请确保始终调用了Begin和End方法。
当操作完成后,传递给BeginInvoke方法的回调函数将被放置到线程池中,确切地说是一个工作者线程中。如果在Main方法定义的结尾注释掉Thread.Sleep方法调用,回调函数将不会被执行。这是因为当主线程完成后,所有的后台线程会被停止,包括该回调函数。对委托和回调函数的异步调用很可能会被同一个工作者线程执行。通过工作者线程ID可以容易地看出。
使用BeginOperationName/EndOperationName方法和.NET中的IAsyncResult对象等方式被称为异步编程模型(或APM模式),这样的方法对称为异步方法。该模式也被应用于多个.NET类库的API中,但在现代编程中,更推荐使用任务并行库(Task Parallel Library,简称TPL)来组织异步API。第4章将会讨论该主题。