CallContext.SetData() - 当线程变为活动 - 非活动 - 活动(TPL)时,对象是否可用?

SFu*_*n28 7 .net .net-4.0 .net-3.5 task-parallel-library

伙计们,

假设我使用来自线程10,11,12的CallContext.SetData()来存储三个新的对象Car实例.这些线程完成执行.然后我执行另一个使用线程10,11,12的多线程操作(可能与第一个不同的操作).GetData()将检索我存储的相同三个对象吗?或者现在上下文有些不同,那些对象消失了?

我的特定用例是任务并行库.我正在使用TPL来并行化一些操作,我想了解在TPL调用之间通过CallContext.SetData()存储的数据会发生什么.

编辑
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;

    }    
}
Run Code Online (Sandbox Code Playgroud)

wag*_*ghe 8

[UPDATE]

实际上,我的原始答案(在这篇文章的底部)似乎有些错误!

我编写了一个小测试程序,用于测试从线程和Tasks,ThreadPool线程以及Parallel.For中的线程在CallContext中存储数据的场景.在Tasks测试和ThreadPool测试中,当重用相同的线程(由ManagedThreadId确定)时,不会再次看到存储在CallContext中的数据.但是,在Parallel.For的情况下,当重用相同的线程(由ManagedThreadId确定)时,再次看到存储在CallContext WAS中的数据.我觉得非常有趣.我不确定这些结果是否是预期的,或者我的程序是否有问题.

要尝试每种情况,只需取消注释所需的测试功能即可.

您将看到Tasks和ThreadPool线程在线程的后续重用中从未遇到CallContext数据而Parallel.For线程DO遇到CallContext数据.

Parallel.For的行为似乎不一致.当我运行Parallel.For的情况下,我可以看到,当重用该线程时,给定的线程不一定总能找到CallContext数据.例如,这是程序的一次运行的输出(使用UseParallelFor取消注释):

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
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,并非如果在重用中找到一个值,它永远保留在CallContext中.在某些情况下,对于给定线程的几次迭代,在CallContext中找不到该值,因此将其添加.然后迭代将报告找到该值.然后,也许,下一次迭代会说没有找到该值.

结果告诉我,您不应该依赖于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);

      });
    }

  }
}
Run Code Online (Sandbox Code Playgroud)

请注意,根据上述测试程序和我的新评论,在这个答案的顶部,我原来的讨论似乎是错误的.从我的测试来看,在Task和ThreadPool线程的情况下,似乎存在于CallContext中的数据在后续重用相同线程id时不可用.但是,看起来存储在CallContext中的数据在Parallel.For的情况下可以重用相同的线程.

结束更新后忽略所有内容.

[结束更新]

我当然不是TPL专家,但我最近一直在关注CallContext.SetData(和LogicalSetData),所以我对CallContext的工作原理有所了解.这里有更好的人来描述在TPL的"上下文"中CallContext数据可能会发生什么或不会发生什么.

我对CallContext.SetData如何工作的理解是,当线程消失时,"数据"将被清除.因此,如果您创建一个新线程,并且在该线程上执行时,使用CallContext.SetData存储一些数据,那么当线程死亡时数据将消失.如果你使用的是ThreadPool线程,那么线程永远不会消亡(好吧,也许永远不会太强),所以通过CallContext.SetData存储的数据在下次有一些代码在(重用)线程中执行时仍然存在.

我的理解是任务并行库在内部使用ThreadPool,因此当再次使用底层ThreadPool线程时,通过CallContext.SetData存储的任何数据可能仍然存在.

编写一个或多个小测试应该很容易,看看当你将数据放入CallContext时会发生什么,然后检查它是否在同一个线程的后续使用中存在.