2011-02-03 9 views
6

フォーク、CallContext.SetData() - スレッドがアクティブ - 非アクティブ - アクティブ(TPL)になったときに使用可能なオブジェクトですか?

スレッド10,11,12からCallContext.SetData()を使用してオブジェクトCarの3つの新しいインスタンスを格納するとします。これらのスレッドは実行を終了します。次に、スレッド10,11,12を使用する別のマルチスレッド操作(おそらく最初のものとは異なる操作)を実行します。GetData()は格納した同じ3つのオブジェクトを取得しますか?あるいは、今やコンテキストがどう違うのですか?

私の特定のユースケースは、タスクパラレルライブラリです。私はいくつかの操作を並列化するためにTPLを使用しており、TPL呼び出しの間にCallContext.SetData()を介して格納されたデータがどうなるかを理解したいと思います。

EDIT
Per @wageogheの提案私はThreadLocalを試してみました。

それを証明するために更新されたコード:

using System; 
using System.Threading; 
using System.Threading.Tasks; 

namespace TlsTest 
{ 

    public class Program 
    { 

     public static void Main() 
     { 
      Console.WriteLine("-------using threadpool---------"); 
      UseThreadPool(); 
      Console.WriteLine("-------using tasks---------"); 
      UseTasks(); 
      Console.WriteLine("-------using parallel for---------"); 
      UseParallelFor(); 
      Console.ReadKey(); 
     } 

     public static void UseThreadPool() 
     { 

      var finish = new CountdownEvent(TotalThreads); 

      for (int i = 0 ; i < TotalThreads ; i++) 
      { 
       ThreadPool.QueueUserWorkItem(x => 
       { 
        int id = Thread.CurrentThread.ManagedThreadId; 

        Thread.Sleep(SleepMilliseconds); 

        if (ThreadId.IsValueCreated) 
        { 
         Console.WriteLine("thread [{0}], tls.thread [{1}] - value already in Tls" , id , ThreadId.Value);       
        } 
        else 
        {       
         Console.WriteLine("thread [{0}] - no Tls value" , id); 
         ThreadId.Value = id; 
        } 
        Thread.Sleep(SleepMilliseconds); 
        finish.Signal(); 
       }); 
      } 
      finish.Wait(); 
     } 

     public static void UseTasks() 
     { 
      const TaskCreationOptions taskCreationOpt = TaskCreationOptions.None; 

      var allTasks = new Task[ TotalThreads ]; 
      for (int i = 0 ; i < TotalThreads ; i++) 
      { 
       Task task = Task.Factory.StartNew(() => 
       { 
        int id = Thread.CurrentThread.ManagedThreadId; 

        Thread.Sleep(SleepMilliseconds); 

        if (ThreadId.IsValueCreated) 
        { 
         Console.WriteLine("thread [{0}], tls.thread [{1}] - value already in Tls" , id , ThreadId.Value); 
        } 
        else 
        { 
         Console.WriteLine("thread [{0}] - no Tls value" , id); 
         ThreadId.Value = id;       
        } 

        Thread.Sleep(SleepMilliseconds); 

       } , taskCreationOpt); 
       allTasks[ i ] = task; 
      } 
      Task.WaitAll(allTasks); 
     } 

     public static void UseParallelFor() 
     { 

      var options = new ParallelOptions(); 
      options.MaxDegreeOfParallelism = 8; 
      Parallel.For(0 , TotalThreads , options , i => 
      { 
       int id = Thread.CurrentThread.ManagedThreadId; 

       Thread.Sleep(SleepMilliseconds); 

       if (ThreadId.IsValueCreated) 
       { 
        Console.WriteLine("thread [{0}], tls.thread [{1}] - value already in Tls" , id , ThreadId.Value); 
       } 
       else 
       { 
        Console.WriteLine("thread [{0}] - no Tls value" , id); 
        ThreadId.Value = id;           
       } 

       Thread.Sleep(SleepMilliseconds); 

      });    
     } 

     private static readonly ThreadLocal<int> ThreadId = new ThreadLocal<int>(); 
     private const int TotalThreads = 100; 
     private const int SleepMilliseconds = 500; 

    }  
} 

答えて

7

[UPDATE]

は実際には、(この記事の下部にある)私のorignalの答えは、部分的に間違っているようです!

私は、スレッドとTasks、ThreadPoolスレッド、およびParallel.ForのスレッドからCallContextにデータを格納するシナリオをテストする小さなテストプログラムを作成しました。 TasksテストとThreadPoolテストの両方で、ManagedThreadIdによって決定された同じスレッドが再利用されたときに、CallContextに格納されたデータが再び表示されませんでした。しかし、Parallel.Forの場合、同じスレッド(ManagedThreadIdによって決定されたもの)が再利用されたときにCallContext WASに格納されたデータが再び表示されます。私はそれが非常に興味深いと分かった。私は、その結果が期待されているか、私のプログラムに何か問題があるかどうかはわかりません。

各ケースを試すには、目的のテスト機能のコメントを外します。

タスクとスレッドプールスレッドは、その後のスレッドの再利用でCallContextデータに遭遇することはありませんが、Parallel.ForスレッドDOはCallContextデータに遭遇します。

Parallel.Forの動作が矛盾しているようです。 Parallel.Forのケースを実行すると、スレッドが再利用されたときに必ずしもCallContextデータが見つかるとは限りません。

thread [9] - no CallContext value 
thread [10] - no CallContext value 
thread [11] - no CallContext value 
thread [12] - no CallContext value 
thread [9], cc.thread [9] - value already in CallContext <-- this is expected as this is the main thread 
thread [10] - no CallContext value 
thread [13] - no CallContext value 
thread [11] - no CallContext value 
thread [12] - no CallContext value 
thread [14] - no CallContext value 
thread [9], cc.thread [9] - value already in CallContext 
thread [10], cc.thread [10] - value already in CallContext 
thread [11], cc.thread [11] - value already in CallContext 
thread [13] - no CallContext value 
thread [15] - no CallContext value 
thread [12], cc.thread [12] - value already in CallContext 
thread [16] - no CallContext value 
thread [14] - no CallContext value 
thread [9], cc.thread [9] - value already in CallContext 
thread [10] - no CallContext value 
thread [17] - no CallContext value 
thread [13], cc.thread [13] - value already in CallContext 
thread [15] - no CallContext value 
thread [11] - no CallContext value 
thread [12] - no CallContext value 
thread [14], cc.thread [14] - value already in CallContext 
thread [18] - no CallContext value 
thread [16] - no CallContext value 
thread [9], cc.thread [9] - value already in CallContext 
thread [10], cc.thread [10] - value already in CallContext 
thread [13] - no CallContext value 
thread [15], cc.thread [15] - value already in CallContext 
thread [11], cc.thread [11] - value already in CallContext 
thread [17] - no CallContext value 
thread [19] - no CallContext value 
thread [18] - no CallContext value 
thread [16], cc.thread [16] - value already in CallContext 
thread [14] - no CallContext value 
thread [20] - no CallContext value 
thread [12], cc.thread [12] - value already in CallContext 
thread [9], cc.thread [9] - value already in CallContext 
thread [10], cc.thread [10] - value already in CallContext 
thread [21] - no CallContext value 
thread [15] - no CallContext value 
thread [11], cc.thread [11] - value already in CallContext 
thread [17], cc.thread [17] - value already in CallContext 
thread [13], cc.thread [13] - value already in CallContext 
thread [19] - no CallContext value 
thread [22] - no CallContext value 
thread [18], cc.thread [18] - value already in CallContext 
thread [16] - no CallContext value 
thread [20] - no CallContext value 
thread [14], cc.thread [14] - value already in CallContext 
thread [12], cc.thread [12] - value already in CallContext 
thread [9], cc.thread [9] - value already in CallContext 
thread [10], cc.thread [10] - value already in CallContext 
thread [23] - no CallContext value 
thread [15], cc.thread [15] - value already in CallContext 
thread [21] - no CallContext value 
thread [11], cc.thread [11] - value already in CallContext 
thread [17] - no CallContext value 
thread [13], cc.thread [13] - value already in CallContext 
thread [19], cc.thread [19] - value already in CallContext 
thread [22] - no CallContext value 
thread [16], cc.thread [16] - value already in CallContext 
thread [18] - no CallContext value 
thread [24] - no CallContext value 
thread [20], cc.thread [20] - value already in CallContext 
thread [14], cc.thread [14] - value already in CallContext 
thread [12], cc.thread [12] - value already in CallContext 
thread [9], cc.thread [9] - value already in CallContext 
thread [10] - no CallContext value 
thread [15], cc.thread [15] - value already in CallContext 
thread [21], cc.thread [21] - value already in CallContext 
thread [17], cc.thread [17] - value already in CallContext 
thread [13], cc.thread [13] - value already in CallContext 
thread [22], cc.thread [22] - value already in CallContext 
thread [18], cc.thread [18] - value already in CallContext 
thread [16], cc.thread [16] - value already in CallContext 
thread [14], cc.thread [14] - value already in CallContext 
thread [9], cc.thread [9] - value already in CallContext 
thread [10], cc.thread [10] - value already in CallContext 
thread [15], cc.thread [15] - value already in CallContext 
thread [17], cc.thread [17] - value already in CallContext 
thread [18], cc.thread [18] - value already in CallContext 
thread [16], cc.thread [16] - value already in CallContext 
thread [9], cc.thread [9] - value already in CallContext 
thread [10], cc.thread [10] - value already in CallContext 
thread [17], cc.thread [17] - value already in CallContext 
thread [18], cc.thread [18] - value already in CallContext 
thread [9], cc.thread [9] - value already in CallContext 
thread [10], cc.thread [10] - value already in CallContext 
thread [9], cc.thread [9] - value already in CallContext 
thread [10], cc.thread [10] - value already in CallContext 
thread [9], cc.thread [9] - value already in CallContext 
thread [10], cc.thread [10] - value already in CallContext 
thread [9], cc.thread [9] - value already in CallContext 
thread [10], cc.thread [10] - value already in CallContext 
thread [9], cc.thread [9] - value already in CallContext 
thread [10], cc.thread [10] - value already in CallContext 

あなたが見ることができるように、それはかつて値は、それが中に残っていることを再利用して発見された場合ではありません:たとえば、ここでは(UseParallelForアンコメント付き)プログラムの一つの実行からの出力がありますCallContextは永遠に。 CallContextに値が見つからないためにスレッドが2回反復されて追加される場合があります。次に、値が見つかると繰り返しが報告されます。次に、次の反復で値が見つからなかったと表示されます。

結果は、スレッドの再利用の間に特定のスレッドが削除されるために、CallContextに残っているデータに頼るべきではないことを示しています。また、Parallel.Forの場合、同じスレッドの再利用の間にCallContextを削除することに頼るべきではないことも教えてくれます。この回答の上部に、私の元議論は間違っているようですが、上記のテストプログラムの光と私の新しいコメントで、

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 

using System.Threading; 
using System.Threading.Tasks; 
using System.Runtime.Remoting.Messaging; 

namespace CallContextTest 
{ 
    class Program 
    { 
    static void Main(string[] args) 
    { 
     //UseTasks(); 
     //UseThreadPool(); 
     UseParallelFor(); 

     Console.ReadKey(); 
    } 

    public static void UseThreadPool() 
    { 
     int totalThreads = 100; 

     CountdownEvent finish = new CountdownEvent(totalThreads); 

     for (int i = 0; i < totalThreads; i++) 
     { 
     int ii = i; 

     ThreadPool.QueueUserWorkItem(x => 
     { 
      int id = Thread.CurrentThread.ManagedThreadId; 

      Thread.Sleep(1000); 

      object o = CallContext.GetData("threadid"); 
      if (o == null) 
      { 
      //Always gets here. 
      Console.WriteLine("thread [{0}] - no CallContext value", id); 
      CallContext.SetData("threadid", id); 
      } 
      else 
      { 
      //Never gets here. 
      Console.WriteLine("thread [{0}], cc.thread [{1}] - value already in CallContext", o, id); 
      } 

      Thread.Sleep(1000); 
      finish.Signal(); 
     }); 

     } 

     finish.Wait(); 
    } 

    public static void UseTasks() 
    { 
     int totalThreads = 100; 
     TaskCreationOptions taskCreationOpt = TaskCreationOptions.None; 
     Task task = null; 


     Task[] allTasks = new Task[totalThreads]; 
     for (int i = 0; i < totalThreads; i++) 
     { 
     int ii = i; 
     task = Task.Factory.StartNew(() => 
     { 
      int id = Thread.CurrentThread.ManagedThreadId; 

      Thread.Sleep(1000); 

      object o = CallContext.GetData("threadid"); 
      if (o == null) 
      { 
      //Always gets here. 
      Console.WriteLine("thread [{0}] - no CallContext value", id); 
      CallContext.SetData("threadid", id); 
      } 
      else 
      { 
      //Never gets here. 
      Console.WriteLine("thread [{0}], cc.thread [{1}] - value already in CallContext", o, id); 
      } 

      Thread.Sleep(1000); 

     }, taskCreationOpt); 
     allTasks[i] = task; 
     } 
     Task.WaitAll(allTasks); 
    } 

    public static void UseParallelFor() 
    { 
     int totalThreads = 100; 
     Parallel.For(0, totalThreads, i => 
     { 
     int ii = i; 
     int id = Thread.CurrentThread.ManagedThreadId; 

     Thread.Sleep(1000); 

     object o = CallContext.GetData("threadid"); 
     if (o == null) 
     { 
      //Sometimes gets here. 
      Console.WriteLine("thread [{0}] - no CallContext value", id); 
      CallContext.SetData("threadid", id); 
     } 
     else 
     { 
      //Sometimes gets here as threads are reused. 
      Console.WriteLine("thread [{0}], cc.thread [{1}] - value already in CallContext", o, id); 
     } 

     Thread.Sleep(1000); 

     }); 
    } 

    } 
} 

注:

は、ここに私のテストプログラムです。私のテストから、CallContextに格納されたデータは、TasksおよびThreadPoolスレッドの場合、同じスレッドIDを後で再利用する際に利用できないように見えます。しかし、CallContextに格納されているデータは、Parallel.Forの場合は同じスレッドを再利用することができます。

終了更新後はすべて無視してください。

[終了更新]

私は確かにTPLの専門家でありませんが、私は最近CallContext.SetData(およびLogicalSetData)で多くのことを見てきたので、私はCallContextがどのように動作するかのいくつかのアイデアを持っています。ここでは、TPLの「コンテキスト」内でCallContextデータが何か起こるかどうかを記述するために、より良い人がいます。

CallContext.SetDataがどのように機能するかについて私が理解することは、スレッドがなくなると "データ"がクリアされるということです。したがって、新しいスレッドを作成し、そのスレッドで実行中にCallContext.SetDataでいくつかのデータを保存すると、スレッドが終了するとデータは消えます。 ThreadPoolスレッドを使用している場合、スレッドは決して死ぬことはありません(まあ、おそらくが強すぎます)ので、CallContext.SetDataを介して格納されたデータは、次回にコードが(再利用された)

私は、Task Parallel LibraryはThreadPoolを内部的に使用しているため、基礎となるThreadPoolスレッドが再び使用されたときにCallContext.SetDataを介して格納されたデータはおそらくそこに残っている可能性があります。

1つ以上の小さなテストを記述するだけで、CallContextにデータを入れたときに何が起こるのかを確認し、同じスレッドの後続の使用でそれがあるかどうかを確認するのは簡単です。

+0

あなたの答えを改訂し、詳細な説明をしてくれてありがとう!あなたの例では、データがCallContextを使って保存されているかどうかに矛盾があることを考えると、当然のことながら、スレッドIDごとに1つのオブジェクトのみを作成する必要があります。私はAppDomainに管理されたスレッドIDプレフィックスを持つキーを持つ値を格納すると思いますか?そして、AppDomain.SetDataはスレッドセーフですか?私はSOの他の人々が呼びかけて、CallContextで何が起こっているのかをお知らせします。思考? – SFun28

+0

@ SFun28 - 私の推測では、特定のスレッドIDがそのスレッドの使用全体にわたって永続的であるために、CallContextに依存してはならないかもしれないということです。 CallContext.SetData経由で何かを保存してからスレッドのどこからでもアクセスすることはできますが、そこに何かを保存し、そのスレッドIDに永久に関連付けることは期待できません。私はそれがあなたがしようとしている効果に依存すると思う。たぶん、元の質問で何をしたいのかを詳しく説明すれば、それを達成する最良の方法で体重測定に役立つかもしれません。 – wageoghe

+1

@ SFun28 - FYI - 今は時間がかかりませんが、テストのためにサンプルコードを使用したいが、Thread.SetData、[ThreadStatic]、ThreadLocalのような他のスレッドローカルファシリティのいくつかを使用して情報を保存したい場合があります http://msdn.microsoft.com/en-us/library/dd642243.aspx)。 CallContext.LogicalSetDataも見てください。これはSetDataに似ていますが、格納するデータは子スレッドにフローします。 LogicalSetDataに関する記事は、Jeffrey Richterのブログ(2010年9月27日)を参照してください。 – wageoghe

関連する問題