1. 程式人生 > >《C#併發程式設計經典例項》—— 轉換.NET事件

《C#併發程式設計經典例項》—— 轉換.NET事件

問題

把一個事件作為 Rx 輸入流,每次事件發生時通過 OnNext 生成資料。

解決方案
Observable 類 定 義 了 一 些 事 件 轉 換 器。 大 部 分 .NET 框 架 事 件 與 FromEventPattern 兼 容, 對於不遵循通用模式的事件,需要改用 FromEvent。

FromEventPattern 最適合使用委託型別為 EventHandler 的事件。很多較新框架類的事 件都採用了這種委託型別。例如,Progress 類定義了事件 ProgressChanged,這個事件 的委託型別就是 EventHandler,因此,它就很容易被封裝到 FromEventPattern:

var  progress = new Progress<int>();
var  progressReports = Observable.FromEventPattern<int>(
handler => progress.ProgressChanged += handler,
 handler => progress.ProgressChanged -=  handler);
progressReports.Subscribe(data => Trace.WriteLine("OnNext:" + data.EventArgs));

請 注 意,data.EventArgs 是 強 類 型 的 int。FromEventPattern 的 類 型 參 數( 上 例 中 為 int) 與 EventHandler 的 T 相同。Rx 用 FromEventPattern 中的兩個 Lambda 引數來實現訂閱 和退訂事件。

較新的 UI 框架採用 EventHandler,可以很方便地應用在 FromEventPattern 中。但是有 些較舊的類常為每個事件定義不同的委託型別。這些事件也能在 FromEventPattern 中使用, 但需要做一些額外的工作。例如,System.Timers.Timer 類有一個事件 Elapsed,它的型別是 ElapsedEventHandler。對此舊類事件,可以用下面的方法封裝進 FromEventPattern:

var  timer = new System.Timers.Timer(interval: 1000)
 {
 Enabled  = true
};
var  ticks = Observable.FromEventPattern<ElapsedEventHandler,  ElapsedEventArgs>(
handler => (s, a) => handler(s, a),
handler => timer.Elapsed += handler,
handler => timer.Elapsed -=  handler);
ticks.Subscribe(data => Trace.WriteLine("OnNext: "  + data.EventArgs.SignalTime));

注意,data.EventArgs 仍然是強型別的。現在 FromEventPattern 的型別引數是對應的事件 處理程式和 EventArgs 的派生類。FromEventPattern 的第一個 Lambda 引數是一個轉換器, 它將 EventHandler 轉換成 ElapsedEventHandler。除了傳遞事件,這個 轉換器不應該做其他處理。

上面程式碼的語法明顯有些彆扭。另一個方法是使用反射機制:

var  timer = new System.Timers.Timer(interval: 1000)  {
Enabled  = true
}; var  ticks = Observable.FromEventPattern(timer, "Elapsed");
 ticks.Subscribe(data => Trace.WriteLine("OnNext: "
+ ((ElapsedEventArgs)data.EventArgs).SignalTime));

採用這種方法後,呼叫 FromEventPattern 就簡單多了。但是這種方法也有缺點:出現了 一個怪異的字串(”Elapsed”),並且訊息的使用者不是強型別了。就是說,這時 data. EventArgs 是 object 型別,需要人為地轉換成 ElapsedEventArgs。

討論
事件是 Rx 流資料的主要來源。本節介紹如何封裝遵循標準模式的事件(標準事件模式: 第一個引數是事件傳送者,第二個引數是事件的型別引數)。對於不標準的事件型別,可 以用過載 Observable.FromEvent 的辦法,把事件封裝進 Observable 物件。

把 事 件 封 裝 進 Observable 對 象 後, 每 次 引 發 該 事 件 都 會 調 用 OnNext。 在 處 理 AsyncCompletedEventArgs 時 會 發 生 令 人 奇 怪 的 現 象, 所 有 的 異 常 信 息 都 是 通 過 數 據 形 式 傳 遞 的(OnNext), 而 不 是 通 過 錯 誤 傳 遞(OnError)。 看 一 個 封 裝 WebClient. DownloadStringCompleted 的例子:

var  client = new WebClient();
var  downloadedStrings = Observable.FromEventPattern(client, "DownloadStringCompleted");
downloadedStrings.Subscribe(
data =>
{
var  eventArgs = (DownloadStringCompletedEventArgs)data.EventArgs;
if (eventArgs.Error !=  null)
Trace.WriteLine("OnNext: (Error) "  + eventArgs.Error);
else
Trace.WriteLine("OnNext: "  + eventArgs.Result);
},
ex  => Trace.WriteLine("OnError: "  + ex.ToString()), () => Trace.WriteLine("OnCompleted"));
client.DownloadStringAsync(new Uri("http://invalid.example.com/"));

WebClient.DownloadStringAsync 出錯並結束時,引發帶有異常 AsyncCompletedEventArgs.Error的事件。可惜 Rx 會把這作為一個數據事件,因此這個程式的結果是顯示“OnNext:(Error)”,
而不是“OnError:”。

有些事件的訂閱和退訂必須在特定的上下文中進行。例如,很多 UI 控制元件的事件必須在 UI 執行緒中訂閱。Rx 提供了一個操作符 SubscribeOn,可以控制訂閱和退訂的上下文。大多數 情況下沒必要使用這個操作符,因為基於 UI 的事件訂閱通常就是在 UI 執行緒中進行的。