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