CallContext.SetData () - an object is available when the thread goes active-inactive-active (TPL)?

People,

Let's say I store three new instances of a Car object using CallContext.SetData () from thread 10, 11, 12. These threads complete the execution. Then I perform another multithreaded operation (possibly a different operation than the first) that uses threads 10, 11, 12. Will GetData () retrieve the same three objects that I saved? Or is the context somehow different now, and these objects go away?

In my particular case, a parallel task library is used. I use TPL to parallelize some operations, and I want to understand what happens to data stored through CallContext.SetData () between TPL calls.

EDIT
As suggested by @wageoghe, I tried ThreadLocal and it worked!

updated code to prove this:

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; } } 
+6
task-parallel-library
source share
1 answer

[UPDATE]

Actually, my orignal answer (at the bottom of this post) seems partially wrong!

I wrote a small test program that checks the script for storing data in CallContext from threads and from tasks, ThreadPool threads and from threads in Parallel.For. In both the Tasks test and the ThreadPool test, the data stored in the CallContext was not viewed again when the same thread (as defined by ManagedThreadId) was reused. However, in the case of Parallel.For, the data stored in the CallContext WAS is again visible when the same thread (as defined by ManagedThreadId) was reused. I found this very interesting. I am not sure if these results are expected or if something is wrong with my program.

To try each case, just uncomment the required test function.

You will see that Task and ThreadPool threads never encounter CallContext data during subsequent repetitions of a thread, while Parallel.For DO threads encounter CallContext data.

The behavior of Parallel.For seems inconsistent. When I run Parallel.For, I see that this thread does not always ALWAYS find CallContext data when this thread is reused. For example, here is the result of a single program run (using UseParallelFor uncommented):

 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 

As you can see, this is not the case as soon as the value was found in reuse, that it remains in CallContext forever. There are several cases where, for several iterations of a given stream, a value is not found in CallContext, so it is added. Then iteration will report that the value is found. Then maybe the next iteration will say that the value was not found.

The results will tell you that you should not rely on the data that remained intact in CallContext, so that this stream is cleared between repetitions of the stream. They also tell me that you should not rely on clearing CallContext between repeats of the same thread in the case of Parallel.For.

Here is my test program:

 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); }); } } } 

Please note that in the light of the above test program and my new comments, at the top of this answer, my initial discussion seems to be wrong. From my test, it turned out that the data stored in CallContext is not available in subsequent retries of the same thread identifier in the case of task threads and ThreadPool. However, it seems that the data stored in CallContext IS is available for reuse of the same stream in the case of Parallel.For.

Do not forget everything after the update is completed.

[End Update]

I'm certainly not an expert on TPL, but I recently watched CallContext.SetData (and LogicalSetData) for a long time, so I have some idea of ​​how CallContext works. There are better people on SO to describe what may or may not happen with CallContext data in the TPL "context".

My understanding of how CallContext.SetData works is that the "data" will be cleared when the stream disappears. So, if you create a new thread and, while executing in this thread, you store some data using CallContext.SetData, then the data will disappear when the thread dies. If you use ThreadPool, the thread never dies (well, maybe it’s never too strong), so the data stored through CallContext.SetData will still be there the next time some code will be executed in the thread).

I also understand that the parallel task library uses ThreadPool internally, so any data stored through CallContext.SetData will probably still be present when the ThreadPool thread is used again.

Simply write one or more small tests to see what happens when you put data in a CallContext, and then check to see if it is in subsequent uses of the same stream.

+7
source share

All Articles