Dot-Net

使用 SynchronizationContext 將事件發送回 WinForms 或 WPF 的 UI

  • June 2, 2014

我正在使用 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()); 
       }));

引用自:https://stackoverflow.com/questions/1949789