[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.