11

私はSynchronizationContextを使用して、多くのマルチスレッドバックグラウンドタスクを実行するDLLからUIスレッドにイベントをマーシャリングします。

シングルトンパターンがお気に入りではないことは知っていますが、今のところ、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());
            }
        }

    }
}

したがって、このサンプルが十分に理解できることを願っています。

4

2 に答える 2

38

差し迫った問題

差し迫った問題はSynchronizationContext.Current、WPFに自動的に設定されないことです。これを設定するには、WPFで実行しているときに、TheUISyncコードで次のようなことを行う必要があります。

var context = new DispatcherSynchronizationContext(
                    Application.Current.Dispatcher);
SynchronizationContext.SetSynchronizationContext(context);
UISync = context;

より深い問題

SynchronizationContextCOM +サポートと連携し、スレッドをクロスするように設計されています。WPFでは、複数のスレッドにまたがるディスパッチャーを使用SynchronizationContextできないため、実際にスレッドを横断することはできません。SynchronizationContextが新しいスレッドに切り替えることができるシナリオはいくつかあります。具体的には、を呼び出すものExecutionContext.Run()です。したがって、SynchronizationContextWinFormsクライアントとWPFクライアントの両方にイベントを提供するために使用している場合は、一部のシナリオが機能しないことに注意する必要があります。たとえば、同じプロセスでホストされているWebサービスまたはサイトへのWeb要求が問題になります。

必要なSynchronizationContextを回避する方法

Dispatcherこのため、WinFormsコードを使用する場合でも、この目的のためだけにWPFのメカニズムを使用することをお勧めします。同期を格納する「TheUISync」シングルトンクラスを作成したので、アプリケーションのトップレベルにフックする方法があることは明らかです。ただし、これを行う場合は、WinFormsアプリケーションにいくつかのWPFコンテンツを追加して機能するようにするコードを追加してから、以下で説明するDispatcher新しいメカニズムを使用できます。Dispatcher

SynchronizationContextの代わりにDispatcherを使用する

WPFのDispatcherメカニズムは、実際には別のSynchronizationContextオブジェクトの必要性を排除します。COM+オブジェクトやWinFormsUIとコードを共有するなど、特定の相互運用シナリオがない限り、最善の解決策は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()); 
        }));

修正する無関係のバグ

また、ここに複製されている元のコードにバグがあることに注意してください。問題は、OnFooDoDoneが呼び出されてからBeginInvoke(またはPost元のコードで)デリゲートが呼び出されるまでの間にFooDoneEventをnullに設定できることです。修正は、デリゲート内の2番目のテストです。

    if(FooDoDoneEvent!=null)
      Application.Current.Dispatcher.BeginInvoke(
        DispatcherPriority.Normal, new Action(() =>
        {
          if(FooDoDoneEvent!=null)
            FooDoDoneEvent(this, new EventArgs()); 
        }));
于 2010-01-12T16:12:07.607 に答える
0

現在のものと比較するのではなく、それについて心配させてみませんか。次に、それは単に「コンテキストなし」の場合を処理する場合です。

static void RaiseOnUIThread(EventHandler handler, object sender) {
    if (handler != null) {
        SynchronizationContext ctx = SynchronizationContext.Current;
        if (ctx == null) {
            handler(sender, EventArgs.Empty);
        } else {
            ctx.Post(delegate { handler(sender, EventArgs.Empty); }, null);
        }
    }
}
于 2010-01-12T13:45:04.777 に答える