為什麼從不同執行緒更新 UI 的模式沒有內置到 .NET 框架中?
我知道“為什麼我的這個框架像/不像 xyz?” 問題有點危險,但我想看看我錯過了什麼。
在 WinForms 中,您不能從另一個執行緒更新 UI。大多數人使用這種模式:
private void EventHandler(object sender, DirtyEventArgs e) { if (myControl.InvokeRequired) myControl.Invoke(new MethodInvoker(MethodToUpdateUI), e); else MethodToUpdateUI(e); } private void MethodToUpdateUI(object obj) { // Update UI }更聰明的是這種模式:
public static TResult SafeInvoke(this T isi, Func call) where T : ISynchronizeInvoke { if (isi.InvokeRequired) { IAsyncResult result = isi.BeginInvoke(call, new object[] { isi }); object endResult = isi.EndInvoke(result); return (TResult)endResult; } else return call(isi); } public static void SafeInvoke(this T isi, Action call) where T : ISynchronizeInvoke { if (isi.InvokeRequired) isi.BeginInvoke(call, new object[] { isi }); else call(isi); }不管使用哪種,每個人都必須編寫樣板程式碼來處理這個令人難以置信的常見問題。那麼,為什麼沒有更新 .NET Framework 來為我們執行此操作?是不是程式碼庫的這個區域被凍結了?是否擔心它會破壞向後兼容性?當某些程式碼在版本 N 中以一種方式工作而在版本 N+1 中以不同方式工作時,是否會引起混淆?
我認為首先提到為什麼會有一個 UI 執行緒可能會很有趣。這是為了降低 UI 組件的生產成本,同時提高它們的正確性和健壯性。
執行緒安全的基本問題是,如果在讀取發生時寫入執行緒已完成一半,則可以觀察到私有狀態的非原子更新在讀取執行緒上已完成一半。
為了實現執行緒安全,您可以做很多事情。
顯式鎖定所有讀寫。優點:最大的靈活性;一切都適用於任何執行緒。缺點:最大的痛苦;一切都必須一直鎖定。可以爭用鎖,這使它們變慢。寫死鎖很容易。編寫無法很好地處理重入的程式碼是很容易的。等等。
只允許在創建對象的執行緒上進行讀寫。您可以在多個執行緒上擁有多個對象,但是一旦線上程上使用了一個對象,那就是唯一可以使用它的執行緒。因此不會同時在不同的執行緒上讀寫,所以你不需要鎖定任何東西。這是“公寓”模型,也是絕大多數 UI 組件建構時所期望的模型。唯一需要鎖定的狀態是不同執行緒上的多個實例共享的狀態,這很容易做到。
只允許在擁有的執行緒上進行讀寫,但當沒有正在進行的讀寫時,允許一個執行緒顯式地將所有權移交給另一個執行緒。這是“租用”模型,它是 Active Server Pages 用來回收腳本引擎的模型。
由於絕大多數 UI 組件都是為在公寓模型中工作而編寫的,並且使所有這些組件都成為自由執行緒是痛苦和困難的,因此您不得不在 UI 執行緒上完成所有 UI 工作。