飞鸽转载异步操作(二)
以下由飛鴿傳書(shū)內(nèi)容轉(zhuǎn)載自網(wǎng)絡(luò),版權(quán)歸原作者所有,作者有可能是ouhujia,歡迎閱讀。
當(dāng)然筆者認(rèn)為觸及這方面知識(shí)就就必須對(duì)委托很了解,這方面編程會(huì)用到委托,同時(shí)還要清楚C#為委托提供的語(yǔ)法便利,筆者發(fā)現(xiàn)異步操作和委托真是“絕配”了。筆者個(gè)人認(rèn)為異步編程也會(huì)是將來(lái)的趨勢(shì),因?yàn)楫惒降某绦?#xff08;不能說(shuō)絕對(duì),但是大多數(shù))效率還是比較高的。我們同時(shí)也在享受異步操作帶來(lái)的優(yōu)勢(shì),WEB服務(wù)器環(huán)境就是一個(gè)異步環(huán)境,每一個(gè)請(qǐng)求都是獨(dú)立的線程。很難想象只能同步處理一個(gè)請(qǐng)求的WEB服務(wù)器有什么用?
定期執(zhí)行受計(jì)算限制的異步操作
??????System.Threadding命名空間中定義了一個(gè)Timer類(lèi),可以使用這個(gè)類(lèi)讓CLR定期地調(diào)用方法。
public sealed class Timer:MarshalByrefObject,IDisposable{
public Timer(TimerCallback callback,Object state,Int32 dueTime,Int32 period);
public Timer(TimerCallback callback,Object state,UInt32 dueTime,Int32 period);
public Timer(TimerCallback callback,Object state,Int64 dueTime,Int64 period);
public Timer(TimerCallback callback,Object state,Timespan dueTime,Timespan period);
}
參數(shù)callback用來(lái)標(biāo)識(shí)希望線程池中的線程回調(diào)方法。回調(diào)線程必須與System.Threading.TimerCallback委托類(lèi)型匹配。構(gòu)造器state參數(shù)允許我們將狀態(tài)數(shù)據(jù)傳遞給回調(diào)方法,如果沒(méi)有狀態(tài)數(shù)據(jù)可傳遞,可以傳遞null。可以使用dueTimer參數(shù)來(lái)告訴CLR在第一次調(diào)用回調(diào)方法時(shí)需要等待多少毫秒。如果希望回調(diào)方法立即被調(diào)用,可以將dueTime參數(shù)設(shè)置成null。最后一個(gè)參數(shù)允許我們指定回調(diào)方法被調(diào)用的時(shí)間間隔。如果該參數(shù)傳遞值為T(mén)imeout.Infinite(-1),那么線程池中的線程只會(huì)調(diào)用回調(diào)方法一次。
所有的Timer對(duì)象到達(dá)后,CLR線程就會(huì)醒來(lái),并且在其內(nèi)部調(diào)用ThreadPool的QueueUserWorkItem方法將一個(gè)條目加入到線程池的隊(duì)列中,從而導(dǎo)致回調(diào)方法被調(diào)用。如果回調(diào)方法需要較長(zhǎng)時(shí)間來(lái)執(zhí)行,那么定時(shí)器可能會(huì)再一次觸發(fā)。這可能到設(shè)置多個(gè)線程池中的對(duì)象同時(shí)執(zhí)行回調(diào)方法。那么我們需要增加一些線程同步鎖來(lái)防止數(shù)據(jù)被破壞。
Timer類(lèi)還提供了Change和Dispose方法,前者允許我們更改或者重新設(shè)置Timer對(duì)象的啟動(dòng)時(shí)間和間隔,后者是取消定時(shí)器,而且可以在所有掛起的回調(diào)方法完成時(shí)選擇性的通知otifyObject參數(shù)標(biāo)識(shí)的內(nèi)核對(duì)象
???? 給出一個(gè)例子,線程池立即開(kāi)始調(diào)用方法,然后每隔2秒調(diào)用一次方法。
using System;using System.Threading;
public static class Program
{
public static void Main()
{
Console.WriteLine(“Main thread:statting a timer”);
Timer t=new Timer(ComputeBoundOp,5,0,2000);
Console.WriteLine(“Main thread:Doing other work here”);
Thread.Sleep(10000);
//取消定時(shí)器
t.Dispose();
}
//該方法由線程執(zhí)行
private static void ComputeBoundOp(Object state)
{
Console.WriteLine(“In ComputeBoundOp:State={0}”,state);
Thread.Sleep(1000);
}
}
異步編程模式APM
????? APM能夠讓我們更好的執(zhí)行異步操作,同時(shí)在FCL中有許多類(lèi)型都支持它
- FileStream 操作:BeginRead 、BeginWrite 。
- DNS 操作:BeginGetHostByName 、BeginResolve 。
- Socket 操作:BeginAccept 、BeginConnect 、BeginReceive 等等。
- WebRequest 操作:BeginGetRequestStream 、BeginGetResponse 。
- SqlCommand 操作:BeginExecuteReader 、BeginExecuteNonQuery 等等。這可能是開(kāi)發(fā)一個(gè)Web 應(yīng)用時(shí)最常用的異步操作。
- WebServcie 調(diào)用操作: 例如.NET 2.0 或WCF 生成的Web Service Proxy 中的BeginXXX 方法、WCF 中ClientBase<TChannel> 的InvokeAsync 方法。
使用ARM執(zhí)行受I/O限制的異步操作
執(zhí)行異步操作時(shí)構(gòu)建高性能,可擴(kuò)展性應(yīng)用程序的關(guān)鍵,它允許我們能夠用非常少的線程來(lái)執(zhí)行許多操作。加上線程池,異步操作允許我們利用機(jī)器中的所有CPU。當(dāng)然這里面存在許多問(wèn)題,因此設(shè)計(jì)了異步編程模式(APM),讓開(kāi)發(fā)人員方便的利用這種能力。
?????? ARM一個(gè)主要特征就是它提供了三個(gè)聚集技巧。回想下使用ThreadPool的QueueUserWorkItem方法時(shí),CLR沒(méi)有內(nèi)置方法來(lái)供我們查找異步操作何時(shí)完成。CLR也沒(méi)有內(nèi)置方法來(lái)發(fā)現(xiàn)異步操作的結(jié)果,也沒(méi)有內(nèi)置方法讓線程池中的線程匯報(bào)結(jié)果。ARM提供了三種機(jī)制,利用這三種機(jī)制,我們可以判斷異步操作時(shí)什么時(shí)候完成的,而且這三個(gè)機(jī)制允許我們獲得一步操作的結(jié)果。
假定希望使用ARM從一個(gè)文件流中異步地讀取一些字節(jié)。首先要調(diào)用System.IO.FileStream對(duì)象的構(gòu)造函數(shù)并接受一個(gè)System.IO.FileOption參數(shù)來(lái)構(gòu)建一個(gè)System.IO.FileStream對(duì)象
對(duì)于System.IO.FileOptions參數(shù)來(lái)說(shuō),我們傳遞一個(gè)FileOption.Asynchronous標(biāo)記,該標(biāo)記告訴FileStream對(duì)象我們準(zhǔn)備在文件上執(zhí)行異步讀/寫(xiě)操作。為了從FileStream對(duì)象中同步的讀取字節(jié),我們可以調(diào)用它的Read方法,該方法的原型如下:
public Int32 Read(Byte[] array,Int32 offset,Int32 count)該方法相信大家已經(jīng)很熟悉了,這里就不多介紹了,但是筆者這里要提醒一點(diǎn)直到讀取所有的字節(jié)都已經(jīng)放到Byte類(lèi)型數(shù)組中,方法才會(huì)返回。同步I/O操作效率很低,因?yàn)镮/O操作的定時(shí)很難預(yù)測(cè)的,并且在等待I/O操作完成時(shí),調(diào)用線程被掛起,因此它不能再做其他任何工作,從而浪費(fèi)資源。如果Windows操作系統(tǒng)已經(jīng)緩存了文件數(shù)據(jù),該方法幾乎可以立即返回。但是如果數(shù)據(jù)沒(méi)有緩存,那么Windows 就不得已與磁盤(pán)驅(qū)動(dòng)器硬件進(jìn)行通信來(lái)加載磁盤(pán)中的數(shù)據(jù)。甚至可能要跨網(wǎng)絡(luò)與服務(wù)器通信,讓服務(wù)器與它的磁盤(pán)驅(qū)動(dòng)器通信得以返回?cái)?shù)據(jù)。因此這里我們可以從文件中異步地讀取字節(jié),可以調(diào)用FileStream的BeginRead方法:
IAsynResult BeginRead(Byte[] array,Int32 offset,Int32 numBytes,AsynCallback userCallback,Object stateObject)
BeginRead方法前三個(gè)參數(shù)與Read方法參數(shù)相同。后面兩個(gè)參數(shù)以后會(huì)提到的。BeginRead方法實(shí)際上講請(qǐng)求加入到Windows設(shè)備驅(qū)動(dòng)程序的隊(duì)列中,而Windows的設(shè)備驅(qū)動(dòng)程序知道如何與正確的硬件設(shè)備通信。就這樣硬件接管了該操作。
BeginRead方法返回一個(gè)其類(lèi)型實(shí)現(xiàn)了System.IAsyncResult接口的對(duì)象引用。調(diào)用該方法時(shí),它構(gòu)建一個(gè)對(duì)象來(lái)唯一的標(biāo)識(shí)I/O操作請(qǐng)求,并將請(qǐng)求加入Windows設(shè)備驅(qū)動(dòng)程序隊(duì)列,然后將IAsyncResult對(duì)象返回給我們。我們可以將IAsyncResult對(duì)象看做收據(jù)。
?????? 事實(shí)上數(shù)據(jù)中是否已經(jīng)包含了所請(qǐng)求的數(shù)據(jù),因?yàn)镮/O操作已經(jīng)被異步地執(zhí)行了。我們不知道什么時(shí)候得到數(shù)據(jù),所以我們需要得到一種方法來(lái)發(fā)現(xiàn)結(jié)果,并且知道什么時(shí)候檢測(cè)到的結(jié)果。上述情況稱(chēng)為異步操作結(jié)果的聚集。
APM的三個(gè)聚集技巧
1.APM的等待直至完成聚集技巧
為了啟動(dòng)一個(gè)異步操作,我們可以調(diào)用一些BeginXxx方法。所有這些方法都會(huì)將請(qǐng)求操作排隊(duì),然后返回一個(gè)IAsyncResult對(duì)象來(lái)標(biāo)識(shí)掛起的操作。為了獲得操作的結(jié)果,我們可以以IAsyncResult對(duì)象為參數(shù)調(diào)用相應(yīng)的EndXxx方法。根據(jù)記錄,所有的EndXxx方法都接受一個(gè)IAsyncResult對(duì)象作為它的一個(gè)參數(shù)。在調(diào)用EndXxx方法時(shí),我們是在請(qǐng)求的CLR返回由IAsyncResult對(duì)象標(biāo)識(shí)的異步操作的結(jié)果。如果異步操作已經(jīng)完成,那么調(diào)用EndXxx方法時(shí),它將立即返回結(jié)果。另一方面如果異步操作沒(méi)有完成,EndXxx方法將掛起調(diào)用線程直至異步操作完成,然后返回結(jié)果。
下面重新考慮從FileStream對(duì)象中讀取字節(jié)的范例
using System;using System.IO;
using System.Threading;
public static class Program
{
public static void Main()
{
//打開(kāi)指示異步I/O操作文件
FileStream fs=new FileStream(@”C:/Boot.ini”,FileMode.Open,FileAccess.Read,
FileShare.Read,1024,FileOptions.Asynchronous);
Byte[] data=new Byte[100];
//為FileStream對(duì)象初始化一個(gè)異步讀操作
IAsyncResult ar=fs.BeginRead(data,0,data.Length,null,null);
//執(zhí)行一些代碼
…
//掛起該線程直至異步操作結(jié)束并獲得結(jié)果
Int32 bytesRead=fs.EndRead(ar);
fs.Close();
//此時(shí)可以確定已經(jīng)讀取了數(shù)據(jù)了(但是結(jié)果都是一些16進(jìn)制的數(shù)據(jù))
}
}
上面的代碼,并沒(méi)有有效地利用ARM。該程序在調(diào)用一個(gè)BeginXxx方法之后立即調(diào)用了一個(gè)EndXxx方法,這樣做了,調(diào)用線程進(jìn)入睡眠狀態(tài),在等待操作的完成。如果希望異步執(zhí)行該操作,可以調(diào)用一個(gè)Read方法。但是如果在BeginRead和EndRead操作之間放一些代碼,會(huì)看到ARM一些價(jià)值,因?yàn)檫@些代碼可以在讀取文件字節(jié)的過(guò)程中執(zhí)行。下面的代碼對(duì)前面的程序做了實(shí)質(zhì)性的修改。新版本的程序同時(shí)從多個(gè)流中讀取數(shù)據(jù)。
private static void ReadMultipleFiles(params String[] pathname){
AsyncStreamRead[] asrs=new AsyncSreamRead[pathnames.Length];
for(Int32 n=0;n<pathnames.Length;n++)
{
//打開(kāi)指示異步I/O操作文件
Sream stream=new FileStream(pathnames[n],FileMode.Open,FileAccess.Read,
FileShare.Read,1024,FileOptions.Asynchronous);
//為Stream 對(duì)象初始化一個(gè)異步操作
asrs[n]=new AsyncStreamRead(stream,100);
}
//所有的流都已經(jīng)打開(kāi),而且所有的讀請(qǐng)求都已經(jīng)排隊(duì),它們都同時(shí)并發(fā)執(zhí)行
//下面獲取并顯示結(jié)果
for(Int32 n=0;n<asrs.Length;n++)
{
Byte[] bytesRead=asrs[n].EndRead();
//顯示結(jié)果
}
}
private sealed class AsyncStreamRead
{
private Sream m_stream;
private IAsyncResult m_ar;
private Byte[] m_data;
public AsyncStreamRead(Stream steram,Int32 numBytes)
{
m_stream=stream;
m_data=new Byte[numBytes];
//為Stream對(duì)象初始化一個(gè)異步操作
m_ar=stream.BeginRead(m_data,0,numByte,null,null);
}
public Byte[] EndRead()
{
//掛起該線程直至獲得結(jié)果
Int32 numBytesRead=m_stream.EndRead(m_ar);
//已經(jīng)沒(méi)有操作執(zhí)行任務(wù),關(guān)閉流
m_stream.Close();
//調(diào)整數(shù)組大小節(jié)省空間
Array.Resize(ref m_data,numBytesRead);
return m_data;
}
}
上代碼同時(shí)執(zhí)行所有讀操作時(shí),將非常有效。但同時(shí)存在低效的地方。ReadMultipleFiles方法按照請(qǐng)求生成次序?yàn)槊總€(gè)流調(diào)用EndRead方法。這種方式效率不高,因?yàn)椴挥玫牧餍枰煌臅r(shí)間來(lái)讀取數(shù)據(jù)。這里完全由可能第二個(gè)流中的數(shù)據(jù)會(huì)在第一個(gè)流中的數(shù)據(jù)之前讀取完成。所以理想情況下,發(fā)生了上述情況,應(yīng)該首先處理第二個(gè)流中的數(shù)據(jù)。
參考網(wǎng)站飛鴿傳書(shū):http://www.freeeim.com/
總結(jié)
以上是生活随笔為你收集整理的飞鸽转载异步操作(二)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 【CComPtr】CComPtr和CCo
- 下一篇: 减小Delphi的Exe文件大小