How to avoid race condition in C#?

Classes in System.Threading namespace and Task Parallel Library [TPL]  introduced in .Net framework 4.0 helps application developer write concurrent, multithreaded, parallel and asynchronous program. Yes I know; I have used quite a few heavy words here, which are often confusing and difficult to differentiate.  So let’s understand the difference between these terminologies first and then we will drill into race condition and thread / task synchronization.

Concurrent – A concurrent system is a one where computation can make progress without waiting for all other computation to complete [extracted from Wikipedia]. However, it’s a generalized term as it does not specify how the computation will be performed. For e.g. whether it will be performed using single or multiple threads, whether it will be performed using one processor or split across multiple processors, whether it will be run in parallel etc.

Multithreaded – A multithreaded program is a way to achieve concurrency. In this case, concurrency can be achieved by dividing the computational work between multiple threads. These threads exist within the context of a single process and can share the process’ resources, but are able to execute independently. Multithreading makes the program efficient since the work gets split between multiple threads however one needs to be very careful about sharing the data between multiple threads which can lead to race condition. Note that a multithreaded environment doesn’t always guarantee efficient program execution as there can be a situation of one thread depending on other thread for continuation. In fact, If not implemented correctly, soon you can find yourself in dealing with problems like deadlocks, unhandled exceptions, race conditions etc.

Parallel – This is another way of achieving concurrency and writing efficient program, however it depends on the hardware. Most of the modern computer these days consists of more than one processor and parallel computing allows application developer to split the computational work at the processor level. .Net framework provides useful APIs to write such parallel programs and all the complexity is completely abstracted from the developer. Assume you have a program to calculate a square root of a number and if you have a quad core machine, it’s possible to compute a square root of four numbers in parallel using such technique!

Asynchronous – It’s the way of writing a non-blocking program to enhance the overall responsiveness of the application.  For e.g. if the application is trying to access a resource over web which can be slow sometimes, in such case if your program makes a synchronous call to the resource, it has to wait until it gets a response from the server. In an asynchronous process, application can continue with other work that does not depend on the response of the web request / resource. .Net framework 4.5 provides many useful APIs along with async , await keywords which allow application developer to write asynchronous program without much code complexity.

Now we have covered basic terminologies, let’s dive into race conditions.

Race Conditions

Race condition exists when the outcome of the program depends on the timing of the events. That means if you run a program today you get one output, if you run it after sometime you get another output, if you run it tomorrow you might get same or different output. Since the program output is inconsistent, you cannot expect guaranteed result which is a dangerous situation for any application. So let’s understand how race condition occurs in a program.

Most common area of race condition is multiple threads accessing a shared resource concurrently. A shared resource can be anything from a variable, object, file etc. Accessing a shared resource in read-only mode is considered as thread-safe operation, however if your program reads and writes shared resources using multiple threads at the same time, then its outcome might be inconsistent, which causes race condition.

Let’s dive into code now.

In below program we have instantiated two threads T1 and T2, which once started prints character ‘+’ and ‘-‘respectively through PrintPlus and PrintMinus functions.

static void Main(string[] args)
{
    Thread T1 = new Thread(PrintPlus);
    T1.Start();

    Thread T2 = new Thread(PrintMinus);
    T2.Start();

    Console.ReadLine();
}

static void PrintPlus()
{
    for (int i = 0; i < 50; i++)
    {
	Console.Write(" + " + "\t");
    }
}

private static void PrintMinus()
{
    for (int i = 0; i < 50; i++)
    {
 	Console.Write(" - " + "\t");
    }
}

Same code can be implemented using TPL.

static void Main(string[] args)
{
	Task.Factory.StartNew(PrintPlus);
	Task.Factory.StartNew(PrintMinus);
	Console.ReadLine();
}

If you think above program will always print 50 ‘+’ signs first and then 50 ‘-‘ signs, I am afraid you’re wrong. Since both the threads / tasks were started in the main method and takes certain amount of time to complete its execution, CPU might allocate some memory for T1 execution first, then pause T1 and execute T2 and so on. So primarily you, as an application developer cannot determine the output of the above program. It will surely print characters [+, -], but order maybe inconsistent.

01 Thread with no shared variable

03 Task without a shared variable

Above program however will never lead into a race condition, since these threads does not share any resource simultaneously.

Let’s make a small modification to the program and share variable counter in both the functions PrintPlus and PrintMinus

private static int counter;

static void Main(string[] args)
{
    var T1 = new Thread(PrintPlus);
    T1.Start();

    var T2 = new Thread(PrintMinus);
    T2.Start();
}

static void PrintPlus()
{
    for (counter = 0; counter < 50; counter++)
    {
 	Console.Write(" + " + "\t");
    }
}

private static void PrintMinus()
{
    for (counter = 0; counter < 50; counter++)
    {
	Console.Write(" - " + "\t");
    }
}

Threads T1 and T2 are both trying to increase the value of ‘counter’ variable and displaying it to Console. Note, there is no dependency between these threads and they will run in parallel. Since both the threads are racing towards shared ‘counter’ variable for read-write operation, either of these threads can execute first which makes program output inconsistent.

02 Thread with shared variable

Implementation of same program using TPL and its output

private static int counter;

static void Main(string[] args)
{
    Task.Factory.StartNew(PrintPlus);
    Task.Factory.StartNew(PrintMinus);
}

static void PrintPlus()
{
    for (counter = 0; counter < 50; counter++)
    {
	Console.Write(" + " + "\t");
    }
}

private static void PrintMinus()
{
    for (counter = 0; counter < 50; counter++)
    {
	Console.Write(" - " + "\t");
    }
}

04 Tasks with a shared variable

So how can we resolve race condition issue? 

Well, if you can, then try to redesign to eliminate shared resources. This is one of the safest solutions; however in a large scale application development, sometime it becomes difficult to analyze the problem area which leads to race condition. For the sample program above, we can define the separate counter variable at the function level which can resolve the problem partially, however to ensure that program always display consistent output, we need to write additional code using thread synchronization method.

Okay, so what is synchronization?

Synchronization

It is a way of coordinating the actions of multiple threads / tasks for avoiding race condition and getting a predictable outcome. It is particularly important when threads access shared resource. .Net provides multiple ways to implement synchronization, lets take a look at some of them.

Synchronization using Thread.Join

Thread.Join method blocks the calling thread until the executing thread terminates. In the program below we have executed T1.Join method before the declaration of thread T2, which ensures that delegate associated with thread T1 [PrintPlus] get executes first before thread T2 starts. In this case we always gets consistent output and eliminates race condition.

static void Main(string[] args)
{
    var T1 = new Thread(PrintPlus);
    T1.Start();
    T1.Join();

    var T2 = new Thread(PrintMinus);
    T2.Start();
    T2.Join();

    // main thread will always execute after T1 and T2 completes its execution
    Console.WriteLine("Ending main thread");
    Console.ReadLine();
}

05 Thread synchronization output

Synchronization using Task.ContinueWith

In some cases, its useful to start a task right after another one completes its execution. TPL ContinueWith method does exactly the same. In the program below, as soon as Task T1 gets completed, Task T2 would get scheduled. Note the ‘antecedent’ parameter to ContinueWith method, which is a handle to the previous task T1.

static void Main(string[] args)
{
    Task T1 = Task.Factory.StartNew(PrintPlus);
    Task T2 = T1.ContinueWith(antacedent => PrintMinus());

    Task.WaitAll(new Task[] { T1, T2 });

    Console.WriteLine("Ending main thread");
}

Above program will generate same output as Thread.Join method output shown earlier

Synchronization using lock

.Net framework provides lock statement using which you can ensure only one thread executes a critical section (code area which leads to race condition) at a time. In our code example, since we are accessing shared variable counter in both the functions PrintPlus and PrintMinus, we can enclose the for loop with the lock statement so that only one thread can execute at a time and other thread can wait for first thread to complete its execution.

static object locker = new object();

static void Main(string[] args)
{
    new Thread(PrintPlus).Start();
    new Thread(PrintMinus).Start();
}

static void PrintPlus()
{
    lock (locker) // Thread safe code
    {
	for (int i = 0; i < 50; i++)
	{
	    Console.Write(" + " + "\t");
	}
    }
}

static void PrintMinus()
{
    lock (locker) // Thread safe code
    {
	for (int i = 0; i < 50; i++)
	{
    	    Console.Write(" - " + "\t");
	}
    }
}

In above case since we have enclosed for loops using lock statements, counter variable will never gets accessed using two threads simultaneously, which will result into consistent output. Above program will generate same output as Thread.Join method output shown earlier

Synchronization using Monitor.Enter – Monitor.Exit

Monitor.Enter and Monitor.Exit work exactly same as the lock statement implemented in earlier approach. In fact, lock statement is makes it just easy to implement thread safe code rather than using Monitor class with try, catch block.

static object locker = new object();

static void Main(string[] args)
{
    new Thread(PrintPlus).Start();
    new Thread(PrintMinus).Start();
}

static void PrintPlus()
{
    Monitor.Enter(locker);
    try
    {
	for (int i = 0; i < 50; i++)
	{
   	    Console.Write(" + " + "\t");
	}
    }
    finally
    {
	Monitor.Exit(locker);
    }
}

static void PrintMinus()
{
    Monitor.Enter(locker);
    try
    {
	for (int i = 0; i < 50; i++)
	{
   	    Console.Write(" - " + "\t");
	}
    }
    finally
    {
	Monitor.Exit(locker);
    }
}

Above program will generate same output as Thread.Join method output shown earlier

Summary

I hope this article was helpful to you to understand race conditions in .Net and how to handle it using different synchronization techniques. Complete source code is available on GitHub.

Thanks for reading.

15 Comments

  1. Al · October 9, 2014 Reply

    great post!

  2. Vikas · May 13, 2015 Reply

    Great Post. Simple and Exact. It helped me a lot.

  3. John M. Cooper · June 29, 2015 Reply

    This explains situations where the threads or processes are known/knowable when a resource is being shared in a manner that might race, and that is very useful. However, my races occur as part of a build process. msbuild is running with the /m switch which means ‘n’ threads are available for building (typically 4 or 8 in my context). Problem comes when C# is linking a large executable or assembly and another thread wants to consume that linker output. At task execution time, I have no idea which of several tasks might be writing or modifying the executable/assembly. If my tasks pretends it is executing sequentially, it will likely consume the linker output before it has finished–or throw an exception because the linker has FileShare.Write and the task wants FileShare.Read. I could avoid the exception by using FileShare.ReadWrite, but I don’t like this because it hides the error and makes it very likely the assembly/executable will be consumed before it is complete.

  4. Subrat Panda · June 30, 2015 Reply

    Simply Best!!!! Thanks for the post..

  5. Anda · July 1, 2015 Reply

    Very well explained.

    Thank you for sharing information!!!

  6. Ravikumar Patel · July 29, 2015 Reply

    Simply awesome. Thank you

  7. Rahul · August 30, 2015 Reply

    Nice article. Easy to understand…

  8. Dipak · September 25, 2015 Reply

    Excellent post

  9. Riadh · October 21, 2015 Reply

    There is an error in your code, using lock there is no shared variable counter in your code, you are using int i in both methods

  10. Paul Zahra · November 26, 2015 Reply

    Very well explained and succinct article… thanks

  11. Anonymous · July 18, 2016 Reply

    Please correct your example for the ‘lock’ synchronization primitive. There is no use of shared variable in the critical section. The loop is having counter variable ‘i’ defined in both methods PrintPlus PrintMinus

  12. Anonymous · July 18, 2016 Reply

    Same issue is with Monitor

  13. Sope · September 15, 2016 Reply

    Really good post. This shows me a sufficient deal of what I need to do to tackle a race condition I’m currently experiencing

  14. Rajasekar Kalidassan · October 26, 2016 Reply

    Nice & Very Simple to Understand.

Leave a Reply