基於事件的異步模式是比 IAsyncResult 模式更高級的一種異步編程模式,也被用在更多的場合。對於相對簡單的應用程序可以直接用 .Net 2.0 新增的 BackgroundWorker 組件來很方便的實現,對於更復雜的異步應用程序則需要自己實現一個符合基於事件的異步模式的類。這兩者對我都是新東西,先從簡單的入手,下一篇裡我再去嘗試復雜類模型的實現
模式概述
支持基於事件的異步模式的類會有若干個 MethodNameAsync 方法表示開始異步操作,並有對應的 MethodNameCompleted 事件。類裡面還可能會有 CancelAsync或 MethodNameAsyncCancel 方法用於取消異步操作,並可以有 ProgressChanged 或 MethodNameProgressChanged事件來跟蹤執行進度。下面分別作一下解釋
MethodNameAsync 方法可以有兩個重載:單調用和多調用,多調用有一個額外的狀態對象參數 userState。userState 參數用來區分各次異步操作,使得我們可以多次調用多調用形式的方法而不需要等待任何異步操作的完成(在學習 IAsyncResult 模式時我把狀態對象僅僅當成傳給回調方法的一個條件來用,可能在使用模式時這麼做並沒有什麼關系,但在實現模式時不把狀態對象用作異常調用的唯一標識而另作他用就值得商榷了)。而單調用形式的方法如果在前一個調用尚未完成時調用將會拋出 InvalidOperationException 異常
如果有多個異步方法,則應使用 CancelAsync 方法來取消掛起的操作,並可使用 userState 來取消指定的掛起任務。如果只有一個異步方法則可以使用 MethodNameAsyncCancel 方法
另外 MSDN 上說:一次只支持一個掛起的操作的方法(如 Method1Async(string param))是不可取消的。這句話我還沒有理解,不可能說是單調用的異步方法就不能取消吧,BackgroundWorker 上都是這樣做的
先不管了,接著看ProgressChanged 事件。它有一個 ProgressChangedEventArgs 參數,事件處理程序通過檢查該參數的 ProgressPercentage屬性來獲取任務完成的百分比。如果有多個異步操作掛起,也可以通過檢查參數的UserState 屬性來分辨操作。如果需要用 ProgressChanged 事件來報告增量結果,則可以把結果保存在派生自 ProgressChangedEventArgs 的類中,並在事件處理程序中使用
BackgroundWorker
BackgroundWorker 很好的符合了事件異步操作模式。它有兩個重載版本的 RunWorkerAsync 方法(均為單調用形式)和 RunWorkerCompleted 事件,並有 CancelAsync 方法以及 ProcessChanged 事件。不同的是 BackgroundWorker增加了 DoWork 事件,在 RunWorkerAsync 方法調用時發生,以達到將實際執行的開始方法與 BackgroundWorker 分離的目的。還需要提一下的是 WorkerReportsProcess 屬性和ReportProcess 方法,前者指示能否報告進度更新,後者引發 ProcessChanged 事件,它們會在接下來的 Demo 裡用到
嘗試
因為平時經常要處理幾十兆的文本文件,這個 Demo 就做一個讀取文件並顯示進度的控制台程序。先看類名和字段
1 2 3 4 5
class
BackgroundWorkerDemo
{
private
BackgroundWorker m_bw;
string
m_FilePath;
}
構造函數接收文件路徑為參數,設置文件路徑並初始化 BackgroundWorker
1 2 3 4 5 6 7 8 9
public
BackgroundWorkerDemo(
string
filePath)
{
m_FilePath = filePath;
m_bw =
new
BackgroundWorker();
m_bw.WorkerReportsProgress =
true
;
m_bw.DoWork +=
new
DoWorkEventHandler(BackgroundWorker_DoWork);
m_bw.ProgressChanged +=
new
ProgressChangedEventHandler(BackgroundWorker_ProgressChanged);
m_bw.RunWorkerCompleted +=
new
RunWorkerCompletedEventHandler(BackgroundWorker_RunWorkerCompleted);
}
接下來看這三個事件的處理程序。每一個事件都有各自的 EventArgs 參數類型,都很簡單就不多說了
第一個 BackgroundWorker_DoWork 方法寫得我有些郁悶。我在方法裡取文件長度,先是直接取 StreamReader.BaseStream.Length 或 FileInfo.Length ,結果卻導致很多文件讀不到 100% 就結束了,不得已改成先把整個文件讀一次得到字符串的長度。這樣的方法當然性能不好了,主要是因為自己對 IO 一直就不夠清楚,等下一個主題重新認識下 IO 再回頭過來改吧。也望有經驗的朋友賜教,感激不盡