你的位置:首页 > 软件开发 > ASP.net > 【C#进阶系列】27 I/O限制的异步操作

【C#进阶系列】27 I/O限制的异步操作

发布时间:2016-05-17 01:00:05
上一章讲到了用线程池,任务,并行类的函数,PLINQ等各种方式进行基于线程池的计算限制异步操作。而本章讲的是如何异步执行I/O限制操作,允许将任务交给硬件设备来处理,期间完全不占用线程和CPU资源。然而线程池仍然扮演着重要的角色,因为各种I/O操作的结果还是要由线程池线程来处理。 ...

上一章讲到了用get='_blank'>线程池,任务,并行类的函数,PLINQ等各种方式进行基于线程池的计算限制异步操作。

而本章讲的是如何异步执行I/O限制操作,允许将任务交给硬件设备来处理,期间完全不占用线程和CPU资源。

然而线程池仍然扮演着重要的角色,因为各种I/O操作的结果还是要由线程池线程来处理。

Windows如何执行同步I/O操作

既然说道异步I/O操作,那么首先可以先看看同步操作是如何执行。

就比如操作硬盘上的一个文件,通过构造一个FileStream对象打开磁盘文件,然后调用Read方法从文件读取数据。

调用Read方法时,线程从托管代码转变为本机/用户模式代码,Read内部调用Win32的ReadFile函数。

ReadFile分配一个小的数据结构,称为I/O请求包(I/O Request Packet,IRP)。

IRP结构初始化后包含的内容有:文件句柄,文件中的偏移量(从这个位置开始读取字节),一个Byte[]数组的地址,要传输的字节数以及其它常规性内容。

然后ReadFile函数将线程从本机/用户模式代码转变为本机/内核模式代码,像内核传递IRP,从而调用Windows内核。根据IRP中的设备句柄,Windows内核知道I/O操作要传送给哪个硬件设备。

因此,Windows将IRP传送给恰当的设备驱动程序的IRP队列。每个设备驱动程序都维护自己的IRP队列,其中包含了机器上运行的所有进程发出的I/O请求。

IRP数据包到达时,设备驱动程序将IRP信息传递给物理硬件设备上安装的电路板。现在,硬件设备将执行请求的I/O操作。

在硬件执行I/O操作期间,发出了I/O请求的线程将无事可做,所以Windows将线程变成睡眠状态,防止它仍然浪费CPU时间。(然而仍然浪费内存,因为它的用户模式栈,内核模式栈,线程环境块和其它数据结构依然在内存中,而且没有东西访问这些内存)。

最终硬件设备会完成I/O操作,然后Windows唤醒线程,将其调度给一个CPU,使它从内核模式返回用户模式,再返回至托管代码。FileStream的Read方法返回一个Int32,指明从文件中读取的字节数,使我们知道在传给Read的Byte[]中,实际能检索到多少字节。

对于Web服务器而言,这么做的话就坑爹了。可以想象,如果有很多用户请求服务器,获取某文件或数据库的信息,在获取时线程阻塞,等待返回,那么就会创建很多线程,如果用户量足够大,服务器根本就不够用。

而当获取到了信息,大量线程被唤醒,那么此时就存在大量的线程,而CPU内核一般不会很多,所以就会频繁切换上下文,这进一步损害了性能。

Windows如何执行异步I/O操作

基于同步I/O操作在某些场景下的坑爹表现, 当然就需要异步操作来解决了。

依然是那个例子,同样是构造一个FileStream去读取文件,然而现在传递一个FileOptions.Asynchronous标志,告诉Windows希望用异步方式进行文件读写。

并且现在不是调用Read而是ReadAsync来读取数据。

ReadAsync内部分配一个Task<Int>来代表用于完成读取操作的代码。

然后ReadAsync调用Win32 ReadFile函数。

ReadFile分配IRP,和前面同步操作一样初始化它,然后传递给windows内核。

Windows内核将IRP放到驱动程序队列中,但线程不再阻塞,而允许返回至你的代码。(这就是异步的好处了)

所以线程能立即从ReadAsync调用中返回。当然此时IRP尚未处理好,所以不能在ReadAsync之后的代码中访问传递的Byte[]中的字节。

ReadAsync之前在内部创建的Task<Int>对象会返回给用户。

可在该对象上调用ContinueWith来登记任务完成时执行的回调方法,然后在回调函数中处理数据。当然也可以用C#的异步函数功能简化代码,以顺序方式写代码(感觉就像是执行同步I/O)。

硬件设备处理好IRP后,会将IRP放到CLR的线程池队列,将来某个时候一个线程池线程会提取完成的IRP并/ 成任务的代码,最终要么设置异常(如果发生错误),要么返回结果(本例代表成功读取字节数的一个Int32)

这样一来,Task对象就知道操作在什么时候完成,代码可以开始运行并安全地访问Byte[]中的数据。

这样不阻塞线程使得资源不至于被过度浪费,同时提高了I/O效率。

C#异步函数

在我写WEB的经历中从来没用过异步函数,倒是以前玩了一段事件Unity3D的时候用过。

实际上在上一章执行定时计算限制操作那个小节就已经用过了,把那个例子粘贴过来了:

     static void Main(string[] args)    {      asyncDoSomething();      Console.Read();    }    private static async void asyncDoSomething() {      while (true) {        Console.WriteLine("time is {0}", DateTime.Now);        //不阻塞线程的前提下延迟两秒        await Task.Delay(2000);//await允许线程返回        //2秒后某个线程会在await后介入并继续循环      }    }

原标题:【C#进阶系列】27 I/O限制的异步操作

关键词:C#

C#
*特别声明:以上内容来自于网络收集,著作权属原作者所有,如有侵权,请联系我们: admin#shaoqun.com (#换成@)。

可能感兴趣文章

我的浏览记录