閱讀156 返回首頁    go 阿裏雲 go 技術社區[雲棲]


《C#並發編程經典實例》—— 轉換.NET事件

問題

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

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

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

1 var  progress = new Progress<int>();
2 var  progressReports = Observable.FromEventPattern<int>(
3 handler => progress.ProgressChanged += handler,
4  handler => progress.ProgressChanged -=  handler);
5 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:

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

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

上麵代碼的語法明顯有些別扭。另一個方法是使用反射機製:

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

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

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

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

01 var  client = new WebClient();
02 var  downloadedStrings = Observable.FromEventPattern(client, "DownloadStringCompleted");
03 downloadedStrings.Subscribe(
04 data =>
05 {
06 var  eventArgs = (DownloadStringCompletedEventArgs)data.EventArgs;
07 if (eventArgs.Error !=  null)
08 Trace.WriteLine("OnNext: (Error) "  + eventArgs.Error);
09 else
10 Trace.WriteLine("OnNext: "  + eventArgs.Result);
11 },
12 ex  => Trace.WriteLine("OnError: "  + ex.ToString()), () => Trace.WriteLine("OnCompleted"));
13 client.DownloadStringAsync(new Uri("https://invalid.example.com/"));

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

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

最後更新:2017-05-23 11:02:40

  上一篇:go  偽共享(False Sharing)
  下一篇:go  《C#並發編程經典實例》—— 發送通知給上下文