Let’s explore the use and implementation of signaling Constructs with EventWaitHandle to implement Thread Synchronization.
Introduction
When we have multiple threads in our program and these threads deal with the shared resources, the concept of thread synchronization comes into the picture.
The techniques are given below, using which we can implement the synchronization of threads.
- Simple blocking methods
Sleep(), Join(), Task.Wait() are simple blocking methods. These are used to block a thread for the specific period of time.
- Locking
There are two types of locking techniques, which are given below.
- Exclusive locking
This technique allows locking the resources for one thread at a time.
Lock(Monitor.Enter/ Monitor.Exit), Mutex, SpinLock are used to implement Exclusive locking.
- Non-Exclusive locking
This technique allows locking the resources for the specific number of threads at a time. Semaphores, SemphoreSlim are used to implement Non-Exclusive locking
- Signaling
This allows the thread to block the resources, until it receives a notification from another thread.
Thread synchronization is achieved here by using synchronization events (AutoResetEvent, ManualResetEvent and CountdownEvent) in combination with EventWaitHandle class.
In this article, we will explore the implementation of Thread Synchronization, using the synchronization events (AutoResetEvent, ManualResetEvent) in combination with EventWaitHandle class
EventWaitHandle
This is a class, which allows the threads to interact with each other by notifying through the signals.
EventWaithandle are the events, which signals and releases one or more waiting threads and once the threads are released, EventWaitHandle is reset; either automatically or manually.
This is implemented by using the members of class given below.
- WaitOne() - Blocks the current thread until a current WaitHandle receives the signal.
- Set() - Sets the state of the event to be signaled, unblocking one or more waiting threads to execute.
- Reset() - Resets the state of the event to non-signaled to block the threads.
AutoResetEvent: Event Wait Handles, which resets automatically
It notifies the thread through the signals and releases one waiting thread at a time and as the thread is released; it resets the WaitHandle automatically, which returns to a non-signaled state. The threads are blocked by calling WaitOne() method, followed by notifying the thread; which is notified or signaled by calling Set() method. As the Set() method is called, one of the waiting thread is released and AutoResetEvent automatically calls Reset() method to reset WaitHandle and returns it to non-signaled state.
You can create an AutoResetEvent in two ways,
- Via Its own Constructor
- static EventWaitHandle _waitHandle = new AutoResetEvent(false);
Passing true as a parameter is equivalent to calling Set() method immediately.
- Via EventWaitHandle’s Constructor
- static EventWaitHandle _waitHandle = new EventWaitHandle(false,EventResetMode.AutoReset);
Let’s take an example to understand it in more detail.
The example given below shows how AutoResetEvent (AutoEvent) signals by calling Set() method on it and after receiving the signals only one thread (Thread_2) among the three is released and rest two are still waiting on AutoResetEvent.
- class AutoResetEventClass
- {
- private static AutoResetEvent AutoEvent = new AutoResetEvent(false);
-
- static void Main()
- {
-
- for (int i = 0; i <= 2; i++)
- {
- Thread t = new Thread(ThreadProc);
- t.Name = "Thread_" + i;
- t.Start();
- }
-
- Thread.Sleep(500);
- Console.WriteLine("Press Enter to signal the waiting threads");
- Console.ReadLine();
-
- AutoEvent.Set();
-
- Thread.Sleep(500);
- Console.ReadLine();
- }
-
-
- private static void ThreadProc()
- {
- string name = Thread.CurrentThread.Name;
-
- Console.WriteLine(name + " starts and calls WaitOne()");
-
- AutoEvent.WaitOne();
-
- Console.WriteLine(name + " Realeased.");
- Thread.Sleep(500);
- }
-
-
- }
Output
Only Thread_2 is released.
ManualResetEvent: Event Wait Handles that resets manually
It notifies the thread through the signals and releases to all the waiting threads. In addition to this, WaitHandle remains in non-signaled state until it is manually reset. The threads are blocked by calling WaitOne() method. Now, the threads are notified or signaled by calling Set() method. As the Set() method is called, it releases all the waiting threads and then a Reset() method is called manually to reset WaitHandle.This returns it to non-signaled state.
Similar to AutoResetEvent, ManualResetEvent is created by two ways, which are given below.
- Via EventWaitHandle’s Constructor
- static EventWaitHandle _waitHandle = new EventWaitHandle(false,EventResetMode.ManualReset);
Let’s take the same example, but this time; we are going to create ManualResetEvent instead of AutoResetEvent to understand the clear difference between them.
- class ManualResetEventClass
- {
- private static ManualResetEvent ManualEvent = new ManualResetEvent(false);
-
- static void Main()
- {
-
- for (int i = 0; i <= 2; i++)
- {
- Thread t = new Thread(ThreadProc);
- t.Name = "Thread_" + i;
- t.Start();
- }
-
- Thread.Sleep(500);
- Console.WriteLine("Press Enter to signal the waiting threads");
- Console.ReadLine();
-
- ManualEvent.Set();
-
- Thread.Sleep(500);
- Console.ReadLine();
- ManualEvent.Reset();
- }
-
-
- private static void ThreadProc()
- {
- string name = Thread.CurrentThread.Name;
-
- Console.WriteLine(name + " starts and calls WaitOne()");
-
- ManualEvent.WaitOne();
-
- Console.WriteLine(name + " Realeased.");
- Thread.Sleep(500);
- }
- }
Output
All three waiting threads are released.
CountdownEvent
This event unblocks the waiting threads after receiving a certain number of signals. CountdownEvent is used in fork-join scenarios.
The diagram given above explains fork-join scenario. You can see Master thread divides its work into three parallel tasks and as all the three tasks gets completed; they again merge into master thread.
CountdownEvent is created via its own constructor, as shown below.
- CountdownEvent countEvent = new CountdownEvent(3);
We give the initial count as a parameter to the constructor, which signifies the number of signals required to unblock a waiting thread.
When the counter reaches to “0”, it releases a waiting thread and the counter is decremented by notifying the CountdownEvent through the signals. This is achieved by calling Signal() method, as shown below.
The code given above will decrement the counter by “3”.
Let’s take the same example, but this time; we are going to create CountdownEvent.
- class CountdownEventClass
- {
- private static CountdownEvent countEvent = new CountdownEvent(3);
- static void Main()
- {
- for (int i = 0; i <= 2; i++)
- {
- Thread t = new Thread(ThreadProc);
- t.Name = "Thread_" + i;
- t.Start();
- }
- countEvent.Wait();
-
- Console.WriteLine("All threads are released");
- Console.ReadLine();
- }
-
- private static void ThreadProc()
- {
- string name = Thread.CurrentThread.Name;
- Console.WriteLine(name);
- countEvent.Signal();
- }
- }
Output
The event releases the threads as it receives three signals.
If the value of initial count is increased to “4”, as shown below:
private static CountdownEvent countEvent = new CountdownEvent(4);
The CountdownEvent will still block the waiting threads as the number of times Signal() method is called is “3” and the required number is “4”.
Output
Conclusion
I hope, this helps you to understand, what is the difference between AutoResetEvent, ManualResetEvent and CountdownEvent and the scenarios for when to choose the right one between them.