使用 SynchronizationContext 將事件發送回 WinForms 或 WPF 的 UI
我正在使用 SynchronizationContext 將事件從我的 DLL 編組回 UI 執行緒,該 DLL 執行大量多執行緒後台任務。
我知道單例模式不是最喜歡的,但我現在使用它來在創建 foo 的父對象時儲存 UI 的 SynchronizationContext 的引用。
public class Foo { public event EventHandler FooDoDoneEvent; public void DoFoo() { //stuff OnFooDoDone(); } private void OnFooDoDone() { if (FooDoDoneEvent != null) { if (TheUISync.Instance.UISync != SynchronizationContext.Current) { TheUISync.Instance.UISync.Post(delegate { OnFooDoDone(); }, null); } else { FooDoDoneEvent(this, new EventArgs()); } } } }這在 WPF 中根本不起作用,TheUISync 實例 UI 同步(從主視窗提供)永遠不會匹配目前的 SynchronizationContext.Current。在 Windows 窗體中,當我做同樣的事情時,它們將在呼叫後匹配,我們將回到正確的執行緒。
我討厭的修復看起來像
public class Foo { public event EventHandler FooDoDoneEvent; public void DoFoo() { //stuff OnFooDoDone(false); } private void OnFooDoDone(bool invoked) { if (FooDoDoneEvent != null) { if ((TheUISync.Instance.UISync != SynchronizationContext.Current) && (!invoked)) { TheUISync.Instance.UISync.Post(delegate { OnFooDoDone(true); }, null); } else { FooDoDoneEvent(this, new EventArgs()); } } } }所以我希望這個範例足夠有意義。
眼前的問題
您的直接問題是
SynchronizationContext.Current不會為 WPF 自動設置。要設置它,您需要在 WPF 下執行時在 TheUISync 程式碼中執行以下操作:var context = new DispatcherSynchronizationContext( Application.Current.Dispatcher); SynchronizationContext.SetSynchronizationContext(context); UISync = context;更深層次的問題
SynchronizationContext與 COM+ 支持相關聯,旨在跨執行緒。在 WPF 中,您不能擁有跨越多個執行緒的 Dispatcher,因此SynchronizationContext不能真正跨執行緒。在許多情況下 aSynchronizationContext可以切換到新執行緒——特別是任何呼叫ExecutionContext.Run(). 因此,如果您使用SynchronizationContext為 WinForms 和 WPF 客戶端提供事件,則需要注意某些情況會中斷,例如對託管在同一程序中的 Web 服務或站點的 Web 請求將是一個問題。如何繞過需要 SynchronizationContext
因此,我建議
Dispatcher專門為此目的使用 WPF 機制,即使使用 WinForms 程式碼也是如此。您已經創建了一個儲存同步的“TheUISync”單例類,因此顯然您有一些方法可以連接到應用程序的頂層。無論您這樣做,您都可以添加程式碼來創建將一些 WPF 內容添加到您的 WinForms 應用程序,以便它Dispatcher可以工作,然後使用Dispatcher我在下面描述的新機制。使用 Dispatcher 而不是 SynchronizationContext
WPF 的
Dispatcher機制實際上消除了對單獨SynchronizationContext對象的需要。除非您有某些互操作場景,例如與 COM+ 對像或 WinForms UI 共享程式碼,否則最好的解決方案是Dispatcher使用SynchronizationContext.這看起來像:
public class Foo { public event EventHandler FooDoDoneEvent; public void DoFoo() { //stuff OnFooDoDone(); } private void OnFooDoDone() { if(FooDoDoneEvent!=null) Application.Current.Dispatcher.BeginInvoke( DispatcherPriority.Normal, new Action(() => { FooDoDoneEvent(this, new EventArgs()); })); } }請注意,您不再需要 TheUISync 對象 - WPF 會為您處理該細節。
如果您對舊
delegate語法更滿意,您可以這樣做:Application.Current.Dispatcher.BeginInvoke( DispatcherPriority.Normal, new Action(delegate { FooDoDoneEvent(this, new EventArgs()); }));一個不相關的錯誤要修復
另請注意,此處複製的原始程式碼中存在錯誤。問題是 FooDoneEvent 可以在呼叫 OnFooDoDone 的時間和
BeginInvoke(或Post在原始程式碼中)呼叫委託的時間之間設置為 null。修復是委託內部的第二個測試:if(FooDoDoneEvent!=null) Application.Current.Dispatcher.BeginInvoke( DispatcherPriority.Normal, new Action(() => { if(FooDoDoneEvent!=null) FooDoDoneEvent(this, new EventArgs()); }));