天天看點

《C#并發程式設計經典執行個體》—— 發送通知給上下文

聲明:本文是《c#并發程式設計經典執行個體》的樣章,感謝圖靈授權并發程式設計網站釋出樣章,禁止以任何形式轉載此文。

問題

rx 盡量做到了線程不可知(thread agnostic)。是以它會在任意一個活動線程中發出通知(例如 onnext)。

但是我們通常希望通知隻發給特定的上下文。例如 ui 元素隻能被它所屬的 ui 線程控制, 是以,如果要根據 rx 的通知來修改 ui,就應該把通知“轉移”到 ui 線程。

解決方案

rx 提供了 observeon 操作符,用來把通知轉移到其他線程排程器。 看下面的例子,使用 interval,每秒鐘産生一個 onnext 通知:

用我的電腦測試,顯示結果為:

ui thread is 9

interval 0 on thread 10

interval 1 on thread 10

interval 2 on thread 11

interval 3 on thread 11

interval 4 on thread 10

interval 5 on thread 11

interval 6 on thread 11

因為 interval 基于一個定時器(沒有指定的線程),通知會線上程池線程中引發,而不是 在 ui 線程中。要更新 ui 元素,可以通過 observeon 輸送通知,并傳遞一個代表 ui 線程 的同步上下文:

observeon 的另一個常用功能是可以在必要時離開 ui 線程。假設有這樣的情況:滑鼠一移

動,就意味着需要進行一些 cpu 密集型的計算。預設情況下,所有的滑鼠移動事件都發 生在 ui 線程,是以可以使用 observeon 把通知移動到一個線程池線程,在那裡進行計算, 然後再把表示結果的通知傳回給 ui 線程:

private void button_click(object sender, routedeventargs e)

{

var uicontext = synchronizationcontext.current;

trace.writeline(“ui thread is ” + environment.currentmanagedthreadid); observable.fromeventpattern<mouseeventhandler, mouseeventargs>(

handler => (s, a) => handler(s, a), handler => mousemove += handler, handler => mousemove -= handler)

.select(evt => evt.eventargs.getposition(this))

.observeon(scheduler.default)

.select(position =>

// 複雜的計算過程。

thread.sleep(100);

var result = position.x + position.y; trace.writeline(“calculated result ” + result + ” on thread ” +

environment.currentmanagedthreadid);

return result;

})

.observeon(uicontext)

.subscribe(x => trace.writeline(“result ” + x + ” on thread ” + environment.currentmanagedthreadid));

}

運作這段代碼的話,就會發現計算過程是線上程池線程中進行的,計算結果在 ui 線程中

顯示。另外,還會發現計算和結果會滞後于輸入,形成等待的隊列,這種現象出現的原因 在于,比起 100 秒 1 次的計算,滑鼠移動的更新頻率更高。rx 中有幾種技術可以處理這 種情況,其中一個常用方法是對輸入流速進行限制,具體會在 5.4 節介紹。

讨論

實際上,observeon 是把通知轉移到一個 rx 排程器上了。本節介紹了預設排程器(即線程 池)和一種建立 ui 排程器的方法。observeon 最常用的功能是移到或移出 ui 線程,但調 度器也能用于别的場合。6.6 節介紹進階測試時,将再次關注排程器。