いろいろ備忘録日記

主に .NET とか Go とか Flutter とか Python絡みのメモを公開しています。

.NET クラスライブラリ探訪-055 (System.Runtime.Remoting.Messaging.CallContext, 実行コンテキスト, 論理呼び出しコンテキスト, ExecutionContext, LogicalSetData, SuppressFlow)


今回は、System.Runtime.Remoting.Messasing.CallContextクラスのLogicalSetData/LogicalGetDataメソッドについて
ちょこっとメモメモ。


すべてのスレッドには、実行コンテキスト (Execution Context) が関連付けられています。
実行コンテキストには

  • そのスレッドのセキュリティ設定 (圧縮スタック、Thread.Principal, Windowsの認証情報)
  • ホスト設定 (HostExecutionContextManager)
  • 論理呼び出しコンテキストデータ (CallContext)

が紐付いています。

その中でも、論理呼び出しコンテキスト (CallContext) は、LogicalSetDataメソッド、LogicalGetDataメソッドを
利用することにより、同じ実行コンテキストを持つスレッド間でデータを共有することができます。


既定では、CLRは起動元のスレッドの実行コンテキストが自動的に伝播されるようにしてくれます。


実行コンテキストの伝播方法は、ExecutionContextクラスを利用することにより変更することができます。
ExecutionContext.SuppressFlowメソッドにはSerucityCriticalAttributeが付与されているので
環境によっては動作しなくなる可能性があることに注意が必要です。特にクライアントアプリでは。
(SerucityCriticalAttributeは、完全信頼を要求する属性)


LogicalSetDataおよびLogicalGetDataメソッドの利用方法はDictionaryと同じ要領です。


以下サンプルです。

  #region CallContextSamples-01
  /// <summary>
  /// 実行コンテキスト(ExecutionContext)と論理呼び出しコンテキスト(CallContext)のサンプルです。
  /// </summary>
  public class CallContextSamples01 : IExecutable
  {
    public void Execute()
    {
      //
      // すべてのスレッドには、実行コンテキスト (Execution Context) が関連付けられている。
      // 実行コンテキストには
      //    ・そのスレッドのセキュリティ設定 (圧縮スタック、Thread.Principal, Windowsの認証情報)
      //    ・ホスト設定 (HostExecutionContextManager)
      //    ・論理呼び出しコンテキストデータ (CallContext)
      // が紐付いている。
      // 
      // その中でも、論理呼び出しコンテキスト (CallContext) は、LogicalSetDataメソッド、LogicalGetDataメソッドを
      // 利用することにより、同じ実行コンテキストを持つスレッド間でデータを共有することができる。
      // 既定では、CLRは起動元のスレッドの実行コンテキストが自動的に伝播されるようにしてくれる。
      //
      // 実行コンテキストの伝播方法は、ExecutionContextクラスを利用することにより変更することができる。
      // ExecutionContext.SuppressFlowメソッドにはSerucityCriticalAttribute
      // が付与されているので、環境によっては動作しなくなる可能性がある。
      // (SerucityCriticalAttributeは、完全信頼を要求する属性)
      //
      var numberOfThreads = 5;
      
      using (var cde = new CountdownEvent(numberOfThreads))
      {
        //
        // メインスレッド上にて、論理呼び出しコンテキストデータを設定.
        //
        CallContext.LogicalSetData("Message", "Hello World");
        
        //
        // 既定の設定のまま (親元のExecutionContextをそのまま継承) で、別スレッド生成.
        //
        ThreadPool.QueueUserWorkItem(ShowCallContextLogicalData, new ThreadData("First Thread", cde));
        
        //
        // 実行コンテキストの伝播方法を変更.
        //   SuppressFlowメソッドは、実行コンテキストフローを抑制するメソッド.
        // SuppressFlowメソッドは、AsyncFlowControlを戻り値として返却する。
        // 抑制した実行コンテキストを復元するには、AsyncFlowControl.Undoを呼び出す。
        //
        AsyncFlowControl flowControl = ExecutionContext.SuppressFlow();
        
        //
        // 抑制された実行コンテストの状態で、別スレッド生成.
        //
        ThreadPool.QueueUserWorkItem(ShowCallContextLogicalData, new ThreadData("Second Thread", cde));
        
        //
        // 実行コンテキストを復元.
        //
        flowControl.Undo();
        
        //
        // 再度、別スレッド生成.
        //
        ThreadPool.QueueUserWorkItem(ShowCallContextLogicalData, new ThreadData("Third Thread", cde));
        
        //
        // 再度、実行コンテキストを抑制し、抑制されている間に論理呼び出しコンテキストデータを変更し
        // その後、実行コンテキストを復元する.
        //
        flowControl = ExecutionContext.SuppressFlow();
        CallContext.LogicalSetData("Message", "Modified....");
        
        ThreadPool.QueueUserWorkItem(ShowCallContextLogicalData, new ThreadData("Fourth Thread", cde));
        flowControl.Undo();
        ThreadPool.QueueUserWorkItem(ShowCallContextLogicalData, new ThreadData("Fifth Thread", cde));
        
        cde.Wait();
      }
    }
    
    void ShowCallContextLogicalData(object state)
    {
      var data = state as ThreadData;
      
      Console.WriteLine(
         "Thread: {0, -15}, Id: {1}, Message: {2}"
        ,data.Name
        ,Thread.CurrentThread.ManagedThreadId
        ,CallContext.LogicalGetData("Message")
      );
      
      data.Counter.Signal();
    }
    
    #region Inner Classes
    class ThreadData
    {
      public string         Name    { get; private set;}
      public CountdownEvent Counter { get; private set;}
      
      public ThreadData(string name, CountdownEvent cde)
      {
        Name    = name;
        Counter = cde;
      }
    }
    #endregion
  }
  #endregion


以下、実行結果の例です。

  Thread: Second Thread  , Id: 4, Message: 
  Thread: First Thread   , Id: 3, Message: Hello World
  Thread: Fourth Thread  , Id: 3, Message: 
  Thread: Fifth Thread   , Id: 3, Message: Modified....
  Thread: Third Thread   , Id: 4, Message: Hello World


別スレッドで処理を行うようにしているので、実行結果もバラバラに
表示される可能性があります。上記の結果を呼び出し順に並べ替えると
以下のようになります。

  Thread: First Thread   , Id: 3, Message: Hello World
  Thread: Second Thread  , Id: 4, Message: 
  Thread: Third Thread   , Id: 4, Message: Hello World
  Thread: Fourth Thread  , Id: 3, Message: 
  Thread: Fifth Thread   , Id: 3, Message: Modified....


実行コンテキストを抑制 (SuppressFlow)している間、LogicalGetDataメソッドから
値が取得できていないことがわかります。


尚、サンプルでは各スレッドの待ち合わせにカウントダウンラッチを利用しています。
CountdownEventクラスについては、以下の記事をご参照ください。


以下、参考資料です。

================================
過去の記事については、以下のページからご参照下さい。

サンプルコードは、以下の場所で公開しています。