第二十八章 I/O限制的異步操做

目錄數組

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資源。

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不容許一個線程更改另外一個進程的後臺處理模式。

相關文章
相關標籤/搜索