Introduction
In this article I will explain about the reset events in the .NET framework. I have chosen to write on this topic as I felt this part is always little confusing for developers. The reset events are the wait handles, which helps in synchronizing threads in the middle of some asynchronous threading operations where multiple threads are executing simultaneously.
Multi-Threading and Thread Synchronization
Multi-Threading is an operation where multiple threads can be spawned in a process after which the processing load can be divided and handed over to each thread. This helps in improving the performance of the application while doing lengthy, data processing intense or time consuming processes.
While doing a multi-threading operation, different threads can be accessing the same resource (shared resource – non thread safe) at the same time; such a situation would lead to a dead lock, racing or undesired behaviors. In order to avoid such nightmare situations in the thread, synchronization is required.
Reset Event Handles in .NET Framework
In the .NET framework there are many techniques to perform thread synchronization, such as lock, monitor, etc. Reset event wait handles are one among them. There are two kinds of reset event wait handles.
1. AutoResetEvent
2. ManualResetEvent
Using the above mentioned wait handles you can make the spawned threads to wait indefinitely until they are signaled in occurrence of an event. Below are some important methods of the reset events and their explanations.
1. WaitOne – Makes all the spawned threads wait indefinitely until they are signaled. It also has an overload where you can set the maximum time in milliseconds until which the threads can wait.
2. Set – This method is used to signal the thread to continue with the processing.
3. Reset – This method is used to un-signal the threads.
Difference Between AutoResetEvent and ManualResetEvent
Now a question may rise, why are two different wait handles required? Here is the difference.
AutoResetEvent:
1. Once signaled only one thread is released.
2. Reset happens automatically after the thread is released.
ManualResetEvent:
1. Once signaled all the threads are released.
2. Reset should be manually done using the Reset method of the wait handle. Until the threads are explicitly un-signaled they will not wait on the Wait methods.
Choosing Between AutoResetEvent and ManualResetEvent
Many developers, even though they have knowledge of the reset event wait handles, still struggle to choose between the two. Here is a simple explanation.
If the threads executing a particular method should be synchronized so that the method resource is accessed by one thread at a time, then use AutoResetEvent. If all the threads should wait for an event to be completed by another thread and can start processing simultaneously after the event is occurred, then go for ManualResetEvent.
Sample Code
In this section I have provided the sample console application code and an explanation of the behavior of the two different wait handles. Below is the code using the AutoResetEvent.
class Program { static AutoResetEvent _autoResetEvent = new AutoResetEvent(false); static void Main(string[] args) { Thread thread1 = new Thread(new ParameterizedThreadStart(PrintNames)); thread1.Name = "Thread1"; thread1.Start("Simon"); Console.WriteLine("Thread1 invoked"); Thread thread2 = new Thread(new ParameterizedThreadStart(PrintNames)); thread2.Name = "Thread2"; thread2.Start("Bear Grylls"); Console.WriteLine("Thread2 invoked"); Thread thread3 = new Thread(new ParameterizedThreadStart(PrintNames)); thread3.Name = "Thread3"; thread3.Start("Les Stroud"); Console.WriteLine("Thread3 invoked"); Console.WriteLine("All the three threads are waiting in AddNames function!"); //Release the first thread and let it take care of releasing the rest _autoResetEvent.Set(); Console.ReadLine(); } static void PrintNames(object name) { //Do some processing //Make all the incoming threads wait until it is signaled. _autoResetEvent.WaitOne(); Console.WriteLine("{0} released!", Thread.CurrentThread.Name); Console.WriteLine("Name given: {0}", name); //Release the next thread _autoResetEvent.Set(); } }
Now run the application and the output is shown in Fig 1.0.
Fig 1.0
From Fig 1.0 you can see that using the auto reset event signaling releases the threads one after the other and this releasing order is not in sequence.
Modify the code a little to use the ManualResetEvent as shown below.
class Program { static ManualResetEvent _manualResetEvent = new ManualResetEvent(false); static void Main(string[] args) { Thread thread1 = new Thread(new ParameterizedThreadStart(PrintNames)); thread1.Name = "Thread1"; thread1.Start("Simon"); Console.WriteLine("Thread1 invoked"); Thread thread2 = new Thread(new ParameterizedThreadStart(PrintNames)); thread2.Name = "Thread2"; thread2.Start("Bear Grylls"); Console.WriteLine("Thread2 invoked"); Thread thread3 = new Thread(new ParameterizedThreadStart(PrintNames)); thread3.Name = "Thread3"; thread3.Start("Les Stroud"); Console.WriteLine("Thread3 invoked"); Console.WriteLine("All the three threads are waiting in AddNames function!"); //Release the first thread and let it take care of releasing the rest _manualResetEvent.Set(); Console.ReadLine(); } static void PrintNames(object name) { //Do some processing //Make all the incoming threads wait until it is signaled. _manualResetEvent.WaitOne(); Console.WriteLine("{0} released!", Thread.CurrentThread.Name); //Try making the threads wait again. This will not succeed as an explicit reset is required for ManualResetEvent _manualResetEvent.WaitOne(); Console.WriteLine("Name given: {0}", name); } }
Run the application and Fig 1.1 is the output window.
Fig 1.1
Notice that all the threads are released in a single signal and also the second WaitOne is not effective as the ManualResetEvent should be reset manually after it is signaled.
Conclusion
Hope this article has explained about the wait handles and using them appropriately based on the situation. Please make use of the comments section to put in your comments.
Happy Reading!