目录数组
28.1 Windows如何执行I/O操做服务器
28.2 C#的异步函数数据结构
28.3 编译器如何将异步函数转换成状态机异步
28.4 异步函数扩展性async
28.5 异步函数和事件处理程序函数
28.6 FCL的异步函数工具
28.7 异步函数和异常处理post
28.8 异步函数的其余功能优化
28.9 应用程序及其线程处理模型编码
28.10 以异步方式实现服务器
28.11 取消I/O操做
28.12 有的I/O操做必须同步进行
28.13 I/O请求优先级
异步执行I/O限制操做,容许将任务交由硬件设备处理,期间彻底不占用线程和CPU资源。
同步I/O操做:程序经过构造一个FIleStream对象来打开磁盘文件,而后调用Read方法从文件中读取数据。调用FileStream的Read方法时,你的线程从托管代码转变为本机/用户模式代码,Read内部调用Win32ReadFile函数。ReadFile分配一个小的数据结构,称为I/O请求包(IRP)。IRP结构初始化后包含的内容有:文件句柄,文件中的偏移量(从这个位置开始读取字节),一个Byte[]数据的地址(数组用读取的字节来填充),要传输的字节数据以及其余常规性内容。而后,ReadFile将你的线程从本机/用户模式代码转变成本机/内核模式代码,向内核传递IRP数据结构,从而调用Windows内核。根据IRP中的设备句柄,Windows内核知道I/O操做要传送给哪一个硬件设备。所以,Windows将IRP传送给恰当的设备驱动程序的IRP队列。每一个设备驱动程序都维护者本身的IRP队列,其中包含了机器上运行的全部进程发出的I/O请求。IRP数据包到达时,设备驱动程序将IPR信息传给物理硬件设备上安装的电路板。如今,硬件设备将执行请求的I/O操做。在硬件设备执行I/O操做期间,发出了I/O请求的线程将无事可作,因此Windows将线程变成睡眠状态,防止它浪费CUP时间。最终,硬件设备会完成I/O操做。而后,Windows会唤醒你的线程,把它调度给一个CPU,使它从内核模式返回用户模式,再返回至托管代码。FileStream的Read方法如今放回一个Int32,指明从文件中读取的实际字节数,使你知道在传给Read的Byte[]中,实际能检索到多少个字节。
I/O异步操做:构造FileSteam对象,传递一个FileOptions.Asynchronous标识, 告诉Windows我但愿文件的读/写操做以异步方式进行。调用ReadAsync从文件中读取数据,它内部分配一个Task<Int32>对象来表明用于完成读取操做的代码。而后调用Win32ReadFile函数。ReadFIle分配IRP,和前面的同步操做同样初始化它,而后把它传给Windows内核。Windows把IRP添加到硬盘驱动程序的IRP队列中。但线程再也不阻塞,而是容许返回至你的代码。可在Task<Int32>上调用ContinueWith来登记任务完成时执行的回调方法,而后在回调方法中处理数据。硬件设备处理好IRP后,会将完成的IRP放到CLR的线程池队列中。未来某个时候,一个线程池线程会提取完成的IRP并执行执行完成任务的代码,最终要么设置异常,要么返回结果。
CLR在初始化时建立一个I/O完成端口。当你打开硬件设备时,这些设备能够和I/O完成端口关联,使设备驱动程序知道将完成的IRP送到哪儿。
将方法标记为async,编译器就会将方法的代码转换成实现了状态机的一个类型。这就容许线程执行状态机中的一些代码并返回,方法不须要一直执行到结束。
异步函数限制:
不能讲应用程序的Main方法转变成异步函数。另外,构造器,属性访问器方法和事件访问器方法不能转变成异步函数。
异步函数不能使用任何out或ref参数。
不能在catch,finally或unsafe块中使用await操做符。
不能在await操做符以前得到一个支持线程全部权或递归的锁,并在await操做符以后释放它。在C# lock语句中使用await,编译器会报错。
在查询表达式中,await操做符只能在初始from子句的第一个集合表达式中使用,或者在join子句的集合表达式中使用。
任什么时候候使用await操做符,编译器都会获取操做数,并尝试在它上面调用GetAwaiter方法。这多是实例方法或扩展方法。调用GetAwaiter方法所返回的对象称为awaiter,正是它将被等待的对象与状态机粘合起来。
状态机得到awaiter后,会查询其IsCompleted属性。若是操做已经以同步方式完成了,属性返回true,而最为一项优化措施,状态机将继续执行并调用awaiter的GetResult方法。该方法要么抛出异常,要么返回结果。若是操做以异步方式完成,IsCompleted将返回false。状态机调用awaiter的OnCompleted方法并向它传递一个委托(引用状态机的MoveNext方法)。如今,状态机容许它的线程回到原地以执行其余代码。未来某个时候,封装了底层任务的awaiter会在完成时调用委托以执行MoveNext。可根据状态机中的字段知道如何到达代码中的正确位置,使方法能从它当初离开的位置继续。这时,代码调用awaiter的GetResult方法。执行将从这里继续,以便对结果进行处理。
在扩展性方面,能用Task对象包装一个未来完成的操做,就能够用await操做符来等待该操做。
用一个类型(Task)来表示各类异步操做对编码有利,由于能够实现组合操做(好比Task的WhenAll和WhenAsy方法)和其余有用的操做。
除了加强使用Task时的灵活性,异步函数另外一个对扩展性有利的地方在于编译器能够在await的任何操做数上调用GetAwaiter。因此操做数不必定是Task对象。能够是任意类型,只要提供了一个能够调用的GetAwatier方法。
异步函数的返回类型通常是Task或Task<TResult>,它们表明函数的状态机完成。但异步函数是能够返回void的。实现异步事件处理程序时,C#编译器容许你利用这个特殊状况简化编码。
方法签名:void EventHandlerCallback(Object sender, EventArgs e);
异步函数很容易分辨,由于规范要求为方法名附加Async后缀。
System.IO.Stream 的全部派生类都提供了ReadAsync,WriteAsync,FlushAsync和CopyToAsync方法。
System.IO.TextReader 的全部派生类都提供了ReadAsync,ReadLineAsynchronous,ReadToEndAsync和ReadBlockAsync方法。System.IO.TextWriter的派生类提供了WriteAsync,WriteLineAsyc和FlushAsync方法。
System.Net.Http.HttpClient 类提供了GetAsync,GetSteamAsync,GetByteArrayAsync,PostAsync,PutAsynchronous,DeleteAsync和其余许多方法。
System.Net.WebRequest 的全部派生类(包括FileWebRequest,FtpWebRequest和HttpWebRequest)都提供了GetRequestStreamAsync和GetRequestAsync方法
System.Data.SqlClient.SqlCommand 类提供了ExecuteDbDataReaderAsync,ExecuteNonQueryAsync,ExecuteReaderAsync,ExecuteScalarAsync和ExecuteXmlReaderAsync方法。
生成Web服务代理类型的工具(好比SvcUtil.exe)也生成XxxAsync方法
Windows设备驱动程序处理异步I/O请求时可能出错:设备驱动程序会向CLR的线程池post已完成的IRP。一个线程池线程会完成Task对象并设置异常。你的状态机方法恢复时,await操做符发现操做失败并引起该异常。
若是状态机出现未处理的异常,那么表明异步函数的Task对象会由于未处理的异常而完成。而后,正在等待该Task的代码会看到异常。但异步函数也可能使用了void返回类型,这是调用者就没办法发现未处理的异常。因此,但返回void的异步函数抛出未处理的异常时,编译器生成的代码将捕捉它,并使用调用者的同步上下文从新抛出它。若是调用者经过GUI线程执行,GUI线程最终将从新抛出异常。从新抛出这个异常一般形成整个进程终止。
VS为异步函数提供了出色的支持。
C#异步lambda表达式。
.Net Framework支持几种不一样的应用程序模型,而每种模型均可能引入了它本身的线程处理模型。控制台应用程序和Windows服务(实际也是控制台应用程序:只是看不见控制台而已)没有引入任何线程才处理模型;换言之,任何线程可在任什么时候候作它想作的任何事情。
但GUI应用程序(包括Windows窗体,WPF,Silverlight和Windows Store应用程序)引入了一个线程处理模型。在这个模型中,UI元素只能由建立它的线程更新。
ASP.NET应用程序容许任何线程作它想作的任何事情。线程池线程开始处理一个客户端的请求时,能够对客户端的语言文化作出假定,从而容许Web服务器对返回的数字,日期和时间进行该语言文化特有的格式化处理。此外,Web服务器还可对客户端的身份标识作出假定,确保只能访问客户端有权访问的资源。
要构建异步ASP.NET Web窗体,在.aspx文件中添加Async=“true”网页指令,并参考System.Web.UI.Page的RegisterAsyncTask方法。
要构建异步ASP.NET MVC控制器,使你的控制器类从System.Web.Mvc.AsyncController派生,让操做方法返回一个Task<ActionResult>便可。
要构建异步ASP.NET处理程序,使你的类从System.Web.HttpTaskAsyncHandler派生,重写其抽象ProcessRequestAsync方法
要构建异步WCF服务,将服务做为异步函数实现,让它返回Task或Task<TResult>。
实现一个WithCancellation方法来扩展Task<TResult>
FCL不能以异步方式打开文件,访问注册表,访问事件日志,获取目录的文件/子目录或者更改文件/目录的属性等
Windows Runtime容许以异步方法执行I/O操做。可使用C#的异步函数功能简化调用这些API时的编码。
Windows容许线程在发出I/O请求时指定优先级,FCL不包含此功能,可采起P/Invoke本机Win32函数的方式。
要调用ThreadIO的BeginBackgrouondProcessing方法,告诉Windows你的线程要发出低优先级I/O请求。这同时会下降线程的CPU调度优先级。可调用EndBackgroupProcessing,或者在BeginBackgroundProcessing返回的值上调用Dispose,使线程恢复为发出普通优先级的I/O请求(以普通CPU调度优先级)。线程只能影响它本身的后台处理模式;Windows不容许线程更改一个线程的后台处理模式。
若是但愿一个进程中的全部线程发出低优先级I/O请求和进行低优先级的CPU调度,可调用BeginBackgroundProcessing,为它的Process参数传递true值,一个进程只能影响它本身的后台处理模式;Windows不容许一个线程更改另外一个进程的后台处理模式。