Thread Synchronization in C# Using AutoResetEvent & ManualResetEvent

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.

The basic flow is:
  1. AutoResetEvent starts in the non-signaled state.
  2. A thread calls WaitOne() and waits.
  3. Another thread performs some work and calls Set().
  4. One waiting thread is released.
  5. AutoResetEvent automatically changes back to the non-signaled state.
  6. The next thread calling WaitOne() must wait for another Set() 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.

Like 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.

The basic flow is:
  1. ManualResetEvent starts in the non-signaled state.
  2. One or more threads call WaitOne() and wait.
  3. Another thread calls Set().
  4. All waiting threads are released.
  5. The event remains in the signaled state.
  6. Additional threads calling WaitOne() can continue without waiting.
  7. 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 useProducer → 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

Kailash Chandra Behera

I am an IT professional with over 12 years of experience in the full software development life cycle for Windows, services, and web-based applications using Microsoft .NET technologies.

Previous Post Next Post

نموذج الاتصال