SynchronizationContext クラス

Last Updated 2011/09/21


SynchronizationContext クラスは、.NET Framework 2.0 に時代からありますが、.NET Framework 4.0 では ThreadPool クラスの大幅な強化にともなって、SynchronizationContext クラスの内容を変わったものと想像します。ただし、ブラックボックスになっていますので、詳しいことは分かりません。.NET Framework 4.0 の SDK でもこのクラスに対する記述は少ないし、コード例もほとんどありません。

並列処理について研究していると、SynchronizationContext クラスの重要性を認識するようになりましたので、何か参考になるものはないかと WEB サイトをうろついていると、これはと思う記事を見つけました。

記事のタイトルは、"Understanding SynchronizationContext (Part I)" ですが、残念ながら WEB サイトは不明です。ともあれ、この記事の一部を要約すると次のようになります。

このクラスはスレッドの枠を超えて、通信するための仕組みである。たとえば、スレッド1で実行中の内容をスレッド2に反映しようとする場合、スレッド1の同期コンテキストオブジェクトをスレッド2に渡すことで可能になる。スレッド2から同期コンテキストオブジェクトの Send または Post メソッドを呼び出せば、それはスレッド1上で実行した場合と同じになる。

この説明をコードを使って補足します。フォームに button1 と textBox を配置し、button1 をクリックすると、別のスレッドを起こしてデリゲートを実行し、その結果を textBox に表示します。

private void button1_Click(object sender, RoutedEventArgs e)
{
  int id = Thread.CurrentThread.ManagedThreadId;
  Debug.WriteLine(String.Format("button1_Click ThreadId = {0}", id));

  // UI スレッドの同期コンテキストオブジェクトを取得する
  var context = SynchronizationContext.Current;

  // 別のスレッドを起こす
  var thread = new Thread(StartProc);
  thread.Name = "* 別スレッド *";

  // スレッドの実行を開始する(UI スレッドの同期コンテキストオブジェクトを引数として渡す)
  thread.Start(context);
}

// 別スレッド上で実行するデリゲート
private void StartProc(object state)
{
  int id = Thread.CurrentThread.ManagedThreadId;
  Debug.WriteLine(String.Format("StartProc ThreadId = {0}", id));

  // UI 同期コンテキスト
  SynchronizationContext context = state as SynchronizationContext;

  if (context != null)
  {
    for (int i = 0; i < 10; ++i)
    {
      // これは処理時間稼ぎのため
      Thread.SpinWait(10000000);

      // UI 同期コンテキストからデリゲートを呼び出すので、UI スレッド上で実行される
      context.Post(AddTextToTextBox, i.ToString());
    }
  }
}

// UI スレッド上で実行するデリゲート
private void AddTextToTextBox(object state)
{
  int id = Thread.CurrentThread.ManagedThreadId;
  Debug.WriteLine(String.Format("AddTextToTextBox ThreadId = {0}", id));

  string text = state as string;
  textBox.Text += text + "\r\n";
}

実行結果:(出力ウインドウに出力)

button1_Click ThreadId = 10
StartProc ThreadId = 3
AddTextToTextBox ThreadId = 10
AddTextToTextBox ThreadId = 10
....
AddTextToTextBox ThreadId = 10
スレッド '* 別スレッド *' (0x390) はコード 0 (0x0) で終了しました。
AddTextToTextBox ThreadId = 10

出力ウインドウに出力されたものを見ると、button1_Click と AddTextToTextBox との ThreadId が同じになっていることが分かります。つまり、これらは同じスレッド上(この場合は UI スレッド)で実行されたことを意味しています。

ここでは SynchronizationContext クラスの役割を理解することが目的ですから、別スレッドで例外が発生した場合の対処法などのテーマは別の項で説明します。

さて、用語の説明の「スレッドプール」の項で説明しましたが、スレッドは一つの SynchronizationContext オブジェクト(以後、簡単のため、同期コンテキストと称します)を作成し、保持します。これはデフォルトの同期コンテキストとなります。コンソールアプリケーションや Windows サービスアプリケーションの場合は ThreadPool スレッドだけを持ちますが、Windows フォームアプリケーションおよび WPF アプリケーションは ThreadPool スレッドと UI スレッドとの 2 つのスレッドが与えられ、UI スレッドも一つの同期コンテキストを持ちます。ちなみに、同期コンテキストの実装クラスは以下のとおりです。

Windows フォームアプリケーションSystem.Windows.Forms.WindowsFormsSynchronizationContext クラス
WPF アプリケーションSystem.Windows.Threading.DispatcherSynchronizationContext クラス

−以上−