156
阿裏雲
技術社區[雲棲]
《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 )
|
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 ) {
|
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( |
06 |
var eventArgs = (DownloadStringCompletedEventArgs)data.EventArgs; |
07 |
if (eventArgs.Error != null )
|
08 |
Trace.WriteLine( "OnNext: (Error) " + eventArgs.Error);
|
10 |
Trace.WriteLine( "OnNext: " + eventArgs.Result);
|
12 |
ex => Trace.WriteLine( "OnError: " + ex.ToString()), () => Trace.WriteLine( "OnCompleted" ));
|
WebClient.DownloadStringAsync 出錯並結束時,引發帶有異常 AsyncCompletedEventArgs.Error的事件。可惜 Rx 會把這作為一個數據事件,因此這個程序的結果是顯示“OnNext:(Error)”,
而不是“OnError:”。
有些事件的訂閱和退訂必須在特定的上下文中進行。例如,很多 UI 控件的事件必須在 UI 線程中訂閱。Rx 提供了一個操作符 SubscribeOn,可以控製訂閱和退訂的上下文。大多數 情況下沒必要使用這個操作符,因為基於 UI 的事件訂閱通常就是在 UI 線程中進行的。
最後更新:2017-05-23 11:02:40