简单理解线程同步上下文
為了線程安全,winform和wpf框架中規定只能使用UI線程操作控件,從其它線程上操作控件就會報跨線程異常。假如有這樣一個場景:點擊按紐,然后開始計算員工薪資,并將計算信息實時展示在一個文本框中,由于計算過程比較耗時,為了不讓界面卡死,我們會將計算方法放在單獨一個線程中。UI代碼如下:
薪資計算類代碼如下:
上面的代碼就不過多解釋了,相信大家都看的懂。按紐點擊后,開啟一個新線程,執行計算,并將更新UI的方法以委托的形式傳給SalaryCalculator類。我們執行一下,如下:
不出意外,報錯了,我們不能在新線程中更新UI線程。一般的做法,我們可以使用Invoke,這個大家應該都用爛了。改寫下ShowMessage,代碼如下:
除了以上辦法,我們還可以使用SynchronizationContext來解決上面的問題。這個類,大家可能比較陌生, 我們來看一下它的定義,如下:
它的定義:提供在各種同步模型中傳播同步上下文中的基本功能。其實它的含義就是對當前線程上下文的封裝,或者叫當前線程所在環境的封裝。封裝的對象可以傳遞至其他線程,然后在其他線程中調用其Post或Send方法,以此來實現線程間的消息傳播。我們使用SynchronizationContext修改上面的代碼,得到的結果都是一樣。代碼如下:
上面的代碼中,先通過SynchronizationContext.Current獲取UI線程的同步上下文對象,然后在計算薪資的線程中使用這個對象的Post方法,這時控制是在UI線程的上下文中執行,所以不會報錯。
講到這里,其實SynchronizationContext的內容就講完了,不過有個點可以再補充下。大家應該知道Task對象有一個ConfigureAwait()方法,用來配置是否同步上下文,我們到這個方法中看一下,代碼如下:
continueOnCapturedContext嘗試將延續任務封送回原始上下文件,默認為true。這里說的原始上下文,其實就是SynchronizationContext,即異步前(await)前所在的線程的同步上下文。我們將Calculate改成異步方法,代碼如下:
我們知道,異步方法在遇到await之前都在當前線程中執行,當執行完await這行后,方法就會退出,然后會將await之后的代碼封裝成委托(可能不太準確,大概這個意思,會產生一個狀態機類,不展開討論)。在執行await時,默認會捕獲當前的線程上下文,然后當執行完Task.Delay(1000)后,上面說的線程上下文就會將剩下的代碼發回(Post/Send)自己的線程執行。大概像下面這個樣子,代碼如下:
上面代碼不一定準確,只是想表達這個意思。如果我們不想剩下來的代碼在原來的上下文中執行,可以將continueOnCapturedContext設為false,這也是微軟推薦的做法。不然會出現一些意想不到的情況,比如死鎖。我們看一下調用的地方,代碼如下:
我們配置了不捕獲上下文,這時代碼是正常運行的。我們再來演示一下經典的死鎖問題吧,如果你還有興趣就接著向下看吧。我們改造下上面的代碼,改成同步等待,并默認捕獲上下文。代碼如下:
我們定義task變量,并去除ConfigureAwait(false),這樣在Calculate中默認會捕獲上下文。下面的task.Wait()會等待task完成,可是我們在線程上下文中又會執行Post方法,這時互相等待,造成死鎖。解決辦法:用到異步的地方都加上ConfigureAwait(false),另一個不要使用Wait方法,用異步就異步到底。
終于講完了,今天講的內容還是很簡單,如果能幫到你一點點,我就會很開心的(能關注下就更好),哈哈。
最后PS一下這個demo的界面圖吧,讓你們看看我的設計能力~~
總結
以上是生活随笔為你收集整理的简单理解线程同步上下文的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 购票啦 | 2020中国.NET开发者峰
- 下一篇: 持续交付二:为什么需要多个环境