Recently, I created a YouTube video discussing a very interesting C# interview question related to Parallel.Invoke, lock, thread safety and race conditions. Surprisingly, even experienced developers often get confused by this question.
In this blog, we will deeply analyze the problem and understand what is actually happening behind the scenes.
public static void ParallelTest()
{
int counter = 0;
Parallel.Invoke(
() =>
{
lock (counter)
{
counter = counter + 1;
}
},
() =>
{
lock (counter)
{
counter = counter + 1;
}
}
);
Console.WriteLine(counter);
}
The correct answer is:
Many developers immediately start thinking about race conditions and thread synchronization. However, the code does not even reach runtime.
The reason is simple:
lock(counter)
Here, counter is an int, which is a value type. But the lock statement in C# requires a reference type object.
Therefore, the compiler throws an error before execution even starts.
Internally, the lock statement works using object monitors. These monitors are associated with reference type objects.
Value types like int are stored differently and cannot safely act as synchronization objects.
Suppose we change the code like this:
public static void ParallelTest()
{
int counter = 0;
Parallel.Invoke(
() =>
{
counter = counter + 1;
},
() =>
{
counter = counter + 1;
}
);
Console.WriteLine(counter);
}
Now the code compiles successfully.
However, this introduces a race condition. Since both threads may try to update the variable simultaneously, the output may become unpredictable.
You may get:
depending on thread scheduling and execution timing.
This is another interesting variation discussed in the video.
object counter = 0;
Now the code compiles because counter is an object reference.
But this creates another dangerous situation.
Inside the lock block, if the value changes, boxing may create a completely new object reference.
That means different threads may actually lock different objects, completely defeating synchronization.
This is one reason why locking directly on changing objects is considered bad practice.
The recommended approach is to use a dedicated private object for locking.
private static readonly object locker = new object();
public static void ParallelTest()
{
int counter = 0;
Parallel.Invoke(
() =>
{
lock (locker)
{
counter++;
}
},
() =>
{
lock (locker)
{
counter++;
}
}
);
Console.WriteLine(counter);
}
This ensures all threads synchronize using the exact same reference object.
I have explained all these concepts in detail in my YouTube video along with practical discussion and interview perspective.
I provide friendly and practical one-to-one mentorship for:
My focus is always on strong fundamentals, real understanding, debugging skills and interview confidence rather than memorizing answers.
I have more than 20 years of experience in .NET technologies, and all sessions are conducted personally in a comfortable one-to-one environment.
🌐 Website: https://supernovaservices.com
To keep every session productive and distraction-free, please follow these simple guidelines:
Following these guidelines helps you focus better and ensures I can deliver the best learning experience in every class.
I prefer to start with a short 10-minute free call so I can understand:
Why? Because course content, teaching pace, and fees all depend on your needs — there’s no “one-size-fits-all” pricing. Please leave your details below, and I’ll get back to you to arrange a convenient time for the call.
Note: Payment is made only after your first class, once you’re completely satisfied. However, fees paid after the first class are non-refundable. This helps maintain scheduling commitments and allows me to reserve your preferred time slot with full attention.