zoukankan      html  css  js  c++  java
  • 第二十八章 I/O限制的异步操作

    目录

    28.1 Windows如何执行I/O操作

    28.2 C#的异步函数

    28.3 编译器如何将异步函数转换成状态机

    28.4 异步函数扩展性

    28.5 异步函数和事件处理程序

    28.6 FCL的异步函数

    28.7 异步函数和异常处理

    28.8 异步函数的其他功能

    28.9 应用程序及其线程处理模型

    28.10 以异步方式实现服务器

    28.11 取消I/O操作

    28.12 有的I/O操作必须同步进行

    28.13 I/O请求优先级

    异步执行I/O限制操作,允许将任务交由硬件设备处理,期间完全不占用线程和CPU资源。

    28.1 Windows如何执行I/O操作

    同步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送到哪儿。

    28.2 C#的异步函数

    将方法标记为async,编译器就会将方法的代码转换成实现了状态机的一个类型。这就允许线程执行状态机中的一些代码并返回,方法不需要一直执行到结束。

    异步函数限制:

    不能讲应用程序的Main方法转变成异步函数。另外,构造器,属性访问器方法和事件访问器方法不能转变成异步函数。

    异步函数不能使用任何out或ref参数。

    不能在catch,finally或unsafe块中使用await操作符。

    不能在await操作符之前获得一个支持线程所有权或递归的锁,并在await操作符之后释放它。在C# lock语句中使用await,编译器会报错。

    在查询表达式中,await操作符只能在初始from子句的第一个集合表达式中使用,或者在join子句的集合表达式中使用。

    28.3 编译器如何将异步函数转换成状态机

     任何时候使用await操作符,编译器都会获取操作数,并尝试在它上面调用GetAwaiter方法。这可能是实例方法或扩展方法。调用GetAwaiter方法所返回的对象称为awaiter,正是它将被等待的对象与状态机粘合起来。

    状态机获得awaiter后,会查询其IsCompleted属性。如果操作已经以同步方式完成了,属性返回true,而最为一项优化措施,状态机将继续执行并调用awaiter的GetResult方法。该方法要么抛出异常,要么返回结果。如果操作以异步方式完成,IsCompleted将返回false。状态机调用awaiter的OnCompleted方法并向它传递一个委托(引用状态机的MoveNext方法)。现在,状态机允许它的线程回到原地以执行其他代码。将来某个时候,封装了底层任务的awaiter会在完成时调用委托以执行MoveNext。可根据状态机中的字段知道如何到达代码中的正确位置,使方法能从它当初离开的位置继续。这时,代码调用awaiter的GetResult方法。执行将从这里继续,以便对结果进行处理。

    28.4 异步函数扩展性

     在扩展性方面,能用Task对象包装一个将来完成的操作,就可以用await操作符来等待该操作。

    用一个类型(Task)来表示各种异步操作对编码有利,因为可以实现组合操作(比如Task的WhenAll和WhenAsy方法)和其他有用的操作。

    除了增强使用Task时的灵活性,异步函数另一个对扩展性有利的地方在于编译器可以在await的任何操作数上调用GetAwaiter。所以操作数不一定是Task对象。可以是任意类型,只要提供了一个可以调用的GetAwatier方法。

    28.5 异步函数和事件处理程序

     异步函数的返回类型一般是Task或Task<TResult>,它们代表函数的状态机完成。但异步函数是可以返回void的。实现异步事件处理程序时,C#编译器允许你利用这个特殊情况简化编码。

    方法签名:void EventHandlerCallback(Object sender, EventArgs e);

    28.6 FCL的异步函数

    异步函数很容易分辨,因为规范要求为方法名附加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方法

    28.7 异步函数和异常处理

    Windows设备驱动程序处理异步I/O请求时可能出错:设备驱动程序会向CLR的线程池post已完成的IRP。一个线程池线程会完成Task对象并设置异常。你的状态机方法恢复时,await操作符发现操作失败并引发该异常。

    如果状态机出现未处理的异常,那么代表异步函数的Task对象会因为未处理的异常而完成。然后,正在等待该Task的代码会看到异常。但异步函数也可能使用了void返回类型,这是调用者就没办法发现未处理的异常。所以,但返回void的异步函数抛出未处理的异常时,编译器生成的代码将捕捉它,并使用调用者的同步上下文重新抛出它。如果调用者通过GUI线程执行,GUI线程最终将重新抛出异常。重新抛出这个异常通常造成整个进程终止。

    28.8 异步函数的其他功能

    VS为异步函数提供了出色的支持。

    C#异步lambda表达式。

    28.9 应用程序及其线程处理模型

     .Net Framework支持几种不同的应用程序模型,而每种模型都可能引入了它自己的线程处理模型。控制台应用程序和Windows服务(实际也是控制台应用程序:只是看不见控制台而已)没有引入任何线程才处理模型;换言之,任何线程可在任何时候做它想做的任何事情。

    但GUI应用程序(包括Windows窗体,WPF,Silverlight和Windows Store应用程序)引入了一个线程处理模型。在这个模型中,UI元素只能由创建它的线程更新。

    ASP.NET应用程序允许任何线程做它想做的任何事情。线程池线程开始处理一个客户端的请求时,可以对客户端的语言文化做出假定,从而允许Web服务器对返回的数字,日期和时间进行该语言文化特有的格式化处理。此外,Web服务器还可对客户端的身份标识做出假定,确保只能访问客户端有权访问的资源。

    28.10 以异步方式实现服务器

     要构建异步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>。

    28.11 取消I/O操作

     实现一个WithCancellation方法来扩展Task<TResult>

    28.12 有的I/O操作必须同步进行

     FCL不能以异步方式打开文件,访问注册表,访问事件日志,获取目录的文件/子目录或者更改文件/目录的属性等

    Windows Runtime允许以异步方法执行I/O操作。可以使用C#的异步函数功能简化调用这些API时的编码。

    28.13 I/O请求优先级

    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不允许一个线程更改另一个进程的后台处理模式。

    每天学习一丢丢
  • 相关阅读:
    HDOJ 1846 Brave Game
    并查集模板
    HDU 2102 A计划
    POJ 1426 Find The Multiple
    POJ 3278 Catch That Cow
    POJ 1321 棋盘问题
    CF 999 C.Alphabetic Removals
    CF 999 B. Reversing Encryption
    string的基础用法
    51nod 1267 4个数和为0
  • 原文地址:https://www.cnblogs.com/terry-1/p/11198677.html
Copyright © 2011-2022 走看看