Multithreading allows a C# application to execute multiple tasks concurrently, improving responsiveness and making better use of available system resources. However, when multiple threads access shared data or resources at the same time, it can lead to race conditions, inconsistent results, and other synchronization problems.
Thread synchronization is the process of coordinating multiple threads so that they can safely access shared resources and execute operations in the required order. C# provides several synchronization mechanisms, including lock, Monitor, Mutex, Semaphore, AutoResetEvent, and ManualResetEvent.
Understanding these mechanisms is useful when building multithreaded applications where threads need to wait for a signal, coordinate their execution, or control when another thread can continue.
In my previous article “Thread Synchronization in Multithreading”, I explained lock, Monitor, Mutex, and Semaphore in detail, along with code examples. You can refer to that article if you want to learn more about thread synchronization and locking mechanisms.
In this article, we will focus on AutoResetEvent and ManualResetEvent, two synchronization primitives that allow one thread to signal another thread when a particular operation or condition has been completed.
What Is AutoResetEvent?
AutoResetEvent is a thread synchronization mechanism in C# that allows one thread to signal another thread to continue execution. It is useful when one thread needs to wait until another thread has completed a particular operation or reached a specific point in its execution.
How AutoResetEvent Works
An AutoResetEvent works by allowing one thread to wait for a signal from another thread. It maintains a signaled or non-signaled state. A thread can call the WaitOne() method of AutoResetEvent to wait for the event to become signaled. When another thread calls Set(), the waiting thread is released, and the AutoResetEvent automatically returns to the non-signaled state.
For example, when an AutoResetEvent is created with false, it starts in the non-signaled state. A thread calling WaitOne() will block until another thread signals the event by calling Set().
When Set() is called by a thread, one waiting thread is released, and the AutoResetEvent automatically changes back to the non-signaled state. If no thread is currently waiting, the signal is retained, allowing the next thread that calls WaitOne() to continue.
AutoResetEventstarts in the non-signaled state.- A thread calls
WaitOne()and waits. - Another thread performs some work and calls
Set(). - One waiting thread is released.
AutoResetEventautomatically changes back to the non-signaled state.- The next thread calling
WaitOne()must wait for anotherSet()signal.
For Example:
AutoResetEvent autoResetEvent = new AutoResetEvent(false);
Thread worker = new Thread(() =>
{
Console.WriteLine("Worker thread is waiting...");
autoResetEvent.WaitOne();
Console.WriteLine("Worker thread received the signal.");
});
worker.Start();
Thread.Sleep(1000);
Console.WriteLine("Main thread is sending the signal...");
autoResetEvent.Set();
In this example, the worker thread waits at WaitOne(). After one second, the main thread calls Set(), which releases the worker thread. Once the worker thread is released, the AutoResetEvent automatically resets to the non-signaled state.
AutoResetEvent Example in C#
The following example demonstrates how AutoResetEvent can be used to coordinate the execution of two worker threads. Each worker thread waits for a signal from the main thread before continuing.
using System;
using System.Threading;
class Program
{
static AutoResetEvent autoResetEvent = new AutoResetEvent(false);
static void Main()
{
Thread worker1 = new Thread(WorkerMethod);
Thread worker2 = new Thread(WorkerMethod);
worker1.Start();
worker2.Start();
Thread.Sleep(1000);
Console.WriteLine("Main thread is sending the first signal.");
autoResetEvent.Set();
Thread.Sleep(1000);
Console.WriteLine("Main thread is sending the second signal.");
autoResetEvent.Set();
worker1.Join();
worker2.Join();
Console.WriteLine("Both worker threads have completed.");
autoResetEvent.Dispose();
}
static void WorkerMethod()
{
Console.WriteLine(
$"Thread {Thread.CurrentThread.ManagedThreadId} is waiting...");
// Wait for the signal
autoResetEvent.WaitOne();
Console.WriteLine(
$"Thread {Thread.CurrentThread.ManagedThreadId} received the signal and is continuing.");
}
}
Important: AutoResetEvent is useful when you want to send a signal that allows one waiting thread to proceed at a time.
What Is ManualResetEvent?
ManualResetEvent is a thread synchronization mechanism in C# that allows one or more threads to wait until another thread signals them to continue execution.
AutoResetEvent, ManualResetEvent has two states:
- Non-signaled – threads calling
WaitOne()will block and wait. - Signaled – waiting threads are allowed to continue execution.
The key difference is that a ManualResetEvent does not automatically reset after releasing waiting threads. Once another thread calls Set(), the event remains in the signaled state until Reset() is explicitly called.
How ManualResetEvent Works
ManualResetEvent works by allowing one or more threads to wait for a signal before continuing their execution. It maintains two states: signaled and non-signaled.
When a ManualResetEvent is created with false, it starts in the non-signaled state. Any thread that calls WaitOne() will block until another thread calls Set().
When Set() is called, the event changes to the signaled state. At this point, all threads currently waiting on the event are released, and any new thread that calls WaitOne() can also continue immediately.
Unlike AutoResetEvent, the event does not automatically return to the non-signaled state. It remains signaled until Reset() is explicitly called.
ManualResetEventstarts in the non-signaled state.- One or more threads call
WaitOne()and wait. - Another thread calls
Set(). - All waiting threads are released.
- The event remains in the signaled state.
- Additional threads calling
WaitOne()can continue without waiting. - Calling
Reset()changes the event back to the non-signaled state.
Example
ManualResetEvent manualResetEvent = new ManualResetEvent(false);
Thread worker1 = new Thread(() =>
{
Console.WriteLine("Worker 1 is waiting...");
manualResetEvent.WaitOne();
Console.WriteLine("Worker 1 has started.");
});
Thread worker2 = new Thread(() =>
{
Console.WriteLine("Worker 2 is waiting...");
manualResetEvent.WaitOne();
Console.WriteLine("Worker 2 has started.");
});
worker1.Start();
worker2.Start();
Thread.Sleep(1000);
Console.WriteLine("Main thread is sending the signal...");
manualResetEvent.Set();
When the main thread calls Set(), both worker threads can proceed because the ManualResetEvent changes to the signaled state and stays signaled. If you want to stop subsequent threads from passing through the event, call manualResetEvent.Reset(). After Reset(), the event returns to the non-signaled state, and threads calling WaitOne() will wait for the next Set().
ManualResetEvent Example in C#
The following example demonstrates how ManualResetEvent can be used to coordinate multiple threads. In this example, two worker threads wait for a signal from the main thread. Once the main thread calls Set(), both waiting threads are allowed to continue.
using System;
using System.Threading;
class Program
{
static ManualResetEvent manualResetEvent = new ManualResetEvent(false);
static void Main()
{
Thread worker1 = new Thread(WorkerMethod);
Thread worker2 = new Thread(WorkerMethod);
worker1.Start();
worker2.Start();
Console.WriteLine("Main thread is performing some work...");
Thread.Sleep(2000);
Console.WriteLine("Main thread is sending the signal.");
manualResetEvent.Set();
worker1.Join();
worker2.Join();
Console.WriteLine("Both worker threads have completed.");
manualResetEvent.Dispose();
}
static void WorkerMethod()
{
Console.WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId} is waiting...");
// Wait until the event is signaled
manualResetEvent.WaitOne();
Console.WriteLine(
$"Thread {Thread.CurrentThread.ManagedThreadId} received the signal and is continuing.");
}
}
Important: ManualResetEvent acts like a gate. Set() opens the gate for multiple threads, and Reset() closes it again.
AutoResetEvent vs ManualResetEvent
AutoResetEvent |
ManualResetEvent |
|
|---|---|---|
| Signal behavior | Automatically resets after releasing one waiter | Stays signaled until explicitly reset |
| Waiters released | Usually one | All waiting threads |
| Reset | Automatic | Manual |
| Typical use | Producer → consumer / one-at-a-time signaling | Broadcast notification |
| Main methods | Set(), WaitOne() |
Set(), Reset(), WaitOne() |
Summary
Thread synchronization is an essential concept in C# for coordinating multiple threads and ensuring that shared resources are accessed safely. In this article, we explored two commonly used synchronization primitives, AutoResetEvent and ManualResetEvent.
AutoResetEvent releases a single waiting thread when Set() is called and then automatically returns to the non-signaled state. In contrast, ManualResetEvent can release multiple waiting threads and remains signaled until Reset() is explicitly called.
Understanding how Set(), Reset(), and WaitOne() work is important for using these synchronization mechanisms effectively. While AutoResetEvent is useful when you want to signal one waiting thread at a time, ManualResetEvent is better suited for scenarios where multiple threads need to be notified that an event has occurred.
Choosing the right synchronization mechanism depends on the requirements of your application. By understanding the differences between AutoResetEvent and ManualResetEvent, along with their common pitfalls and best practices, you can build more reliable and thread-safe C# applications.
Thanks