Is Your C# Code Thread-Safe?
At its most basic level, a thread is an independent path of execution within an application. By "independent," I mean that the thread acts a little like a mini-program, but it’s important to understand that all threads are in fact owned by a given application. In other words, a thread can’t just come into existence spontaneously, do some work, and then vanish. Rather, all threads are explicitly created by an application and then managed by the runtime environment—specifically, the Common Language Runtime (CLR).
So what is meant by "thread safety"? Basically, it’s the way in which shared resources are handled by multiple threads of execution. If your shared resources (for instance, data members) are handled correctly in your thread code, then your code is thread-safe. If you don’t take care of your shared resources, then they may become corrupted by your thread code; in this case, the code is not thread-safe. To understand why this is so, we need to look at how an operating system manages the problem of thread scheduling.
When you implement a thread, the precise execution profile of that thread isn’t really under your control. The problem of thread scheduling is handled by the operating system. You can create two types of threads—foreground or background—but this won’t really give your thread a better chance of executing, as compared with any other thread. A foreground thread must be ended before the CLR will terminate the containing application. A background thread, on the other hand, will be terminated automatically when the containing application ends.
A thread-safe implementation basically protects shared data. Imagine that you have a C# class with an integer member variable. If the class is instantiated once and then the associated object reference is accessed by more than one thread, there is scope for both threads making changes to the member variable. Specifically, two or more threads may attempt to increment or otherwise modify the same member variable. Unfortunately, unless you have made programmatic provision explicitly for this situation, no single thread is guaranteed to have exclusive modification access to the member variable. In other words, let’s say that Thread 1 increments the data member at the same time that Thread 2 is trying to increment the data member. In such a case, the operating system thread scheduler is at liberty to switch out Thread 1 and switch in Thread 2 at any moment. The switch can occur before Thread 1 has completed its increment operation. Such an unprotected implementation is said to be thread-unsafe.
If the use of threads is so complicated and fraught with such difficulties, why bother creating threads at all?