Reputation: 1015
I have just hit something weird when I was exploring Threads and Tasks using ThreadStatic Attribute. I believe this may be very specific to Threads and Tasks. Consider the below code snippet:
[ThreadStatic]
static int range=10;
Action action = () =>
{Console.WriteLine("Thread = {0}, Value = {1}", Thread.CurrentThread.ManagedThreadId, range);
Parallel.Invoke( action, action);
This gives the output:
Thread = 2, Value = 10
Thread = 3, Value = 0
This is absolutely fine as ThreadStatic variables can only be initialized once and so, the second line shows it as 0.
But, consider the below scenario:
[ThreadStatic]
static int range=10;
new Thread(() =>
{
Console.WriteLine("Thread = {0}, Value = {1}" Thread.CurrentThread.ManagedThreadId, range);
}).Start();
new Thread(() =>
{
Console.WriteLine("Thread = {0}, Value = {1}" Thread.CurrentThread.ManagedThreadId, range);
}).Start();
This line gives me the output:
Thread = 6, Value = 0
Thread = 7, Value = 0
How much ever Thread I span, I couldn't really see the 'range' value being initialized and displayed as 10. Where is the range variable being initialized here and why there is discrimination between Threads and Tasks in initializing the static variables?
Am I missing something basic here ? Thanks in advance.
Upvotes: 3
Views: 1287
Reputation: 941465
Your [ThreadStatic] is initialized by the static constructor of the class that contains this code. By the thread that creates the instance of the class or uses a static member, whichever is first. So by definition, the two new threads you create can never see the initial value.
The quirky behavior is actually in the first snippet. What you didn't count on is that Parallel.Invoke() also uses the thread that calls Invoke() to do part of the job. So it can actually see the initial value. Rewriting the code a bit can show you this:
class Test {
[ThreadStatic]
static int range=10;
public static void Run() {
Action action = () => {
Console.WriteLine("Thread = {0}, Value = {1}", Thread.CurrentThread.ManagedThreadId, range);
};
Console.WriteLine("Start thread = {0}, Value = {1}", Thread.CurrentThread.ManagedThreadId, range);
Parallel.Invoke(action, action);
}
}
Output:
Start thread = 8, Value = 10
Thread = 8, Value = 10
Thread = 9, Value = 0
Not a real problem of course, you can't use [ThreadStatic] in Parallel code.
Upvotes: 3
Reputation: 51330
In the first case, the range
variable has been initialized to 10
by the thread which initialized the class (the one which ran the static constructor). It will be equal to 0
on all other threads. And you call Parallel.Invoke
from this same thread.
Instead of spawning two threads, Parallel.Invoke
will invoke the first action on the current thread, and the second one on a threadpool Task
. Since it returns once all actions are done, there's no need to use 3 threads for this, one of them being blocked waiting for the 2 others to finish.
You can find the relevant code in the reference source:
// Optimization: Use current thread to run something before we block waiting for all tasks.
tasks[0] = new Task(actionsCopy[0]);
tasks[0].RunSynchronously(parallelOptions.EffectiveTaskScheduler);
Your second snippet spawns 2 threads, so no Console.WriteLine
will ever be executed from the inital thread. You'll never see the 10
value in this case.
Upvotes: 1