Synchronizing Threads with AutoResetEvent in C# .NET

In multithreaded applications, synchronizing the execution of threads is crucial to avoid race conditions and ensure that shared resources are accessed in a controlled manner. One way to achieve this synchronization in C# is by using the AutoResetEvent class. This article will explain how to use AutoResetEvent for thread synchronization, illustrated by practical examples.

What is AutoResetEvent?

AutoResetEvent is a synchronization primitive that can be used to manage the execution order of threads. It operates like a gate that is either open (signaled) or closed (non-signaled). When a thread calls WaitOne() on an AutoResetEvent that is not signaled, it blocks until another thread calls Set(), which signals the event and allows the waiting thread to proceed. Once a thread has passed through the gate, AutoResetEvent automatically resets to the non-signaled state, blocking any subsequent threads until Set() is called again.

Example 1. Coordinating Multiple Threads

In the next example, we'll demonstrate how to use AutoResetEvent to coordinate the execution of multiple threads. Here, we have three threads that need to run in a specific order: Thread 1, followed by Thread 2, and then Thread 3.

using System;
using System.Threading;
namespace MultiThreadSynchronization
{
    class Program
    {
        private static AutoResetEvent event1 = new AutoResetEvent(false);
        private static AutoResetEvent event2 = new AutoResetEvent(false);
        static void Main(string[] args)
        {
            Thread t1 = new Thread(Thread1);
            Thread t2 = new Thread(Thread2);
            Thread t3 = new Thread(Thread3);

            t1.Start();
            t2.Start();
            t3.Start();

            // Start the first thread
            event1.Set();

            Console.Read();
        }
        static void Thread1()
        {
            event1.WaitOne();
            Console.WriteLine("Thread 1 is running");
            // Signal the second thread to start
            event2.Set();
        }
        static void Thread2()
        {
            event2.WaitOne();
            Console.WriteLine("Thread 2 is running");
            // Signal the third thread to start
            event1.Set();
        }
        static void Thread3()
        {
            event1.WaitOne();
            Console.WriteLine("Thread 3 is running");
        }
    }
}

Example 2. Real-Life use case processing orders in sequence

Consider an e-commerce system where we need to process customer orders in sequence. Each order goes through several stages: validation, payment processing, and shipment. These stages should be executed in order, and each stage should start only when the previous stage is complete.

using System;
using System.Threading;
namespace OrderProcessing
{
    class Program
    {
        private static AutoResetEvent validationEvent = new AutoResetEvent(false);
        private static AutoResetEvent paymentEvent = new AutoResetEvent(false);
        private static AutoResetEvent shipmentEvent = new AutoResetEvent(false);
        static void Main(string[] args)
        {
            Thread validationThread = new Thread(ValidateOrder);
            Thread paymentThread = new Thread(ProcessPayment);
            Thread shipmentThread = new Thread(ShipOrder);
            validationThread.Start();
            paymentThread.Start();
            shipmentThread.Start();
            // Start the validation process
            validationEvent.Set();

            Console.Read();
        }
        static void ValidateOrder()
        {
            validationEvent.WaitOne();
            Console.WriteLine("Validating order...");
            Thread.Sleep(2000); // Simulate order validation
            Console.WriteLine("Order validated.");
            // Signal the payment processing thread to start
            paymentEvent.Set();
        }
        static void ProcessPayment()
        {
            paymentEvent.WaitOne();
            Console.WriteLine("Processing payment...");
            Thread.Sleep(2000); // Simulate payment processing
            Console.WriteLine("Payment processed.");
            // Signal the shipment thread to start
            shipmentEvent.Set();
        }
        static void ShipOrder()
        {
            shipmentEvent.WaitOne();
            Console.WriteLine("Shipping order...");
            Thread.Sleep(2000); // Simulate order shipment
            Console.WriteLine("Order shipped.");
        }
    }
}

Explanation
 

Initialization

Three AutoResetEvent instances are created: validationEvent, paymentEvent, and shipmentEvent, all in a non-signaled state (false).

Thread Coordination

  • The validation thread starts first and signals the payment processing thread once validation is complete.
  • The payment processing thread waits for the signal from the validation thread, processes the payment, and then signals the shipment thread.
  • The shipment thread waits for the signal from the payment processing thread and then ships the order.

Benefits of using AutoResetEvent

  • Synchronization: Ensures that threads run in a controlled sequence.
  • Automatic Reset: Automatically resets to the non-signaled state after a waiting thread is released, making it ideal for one-off signaling.
  • Thread Safety: Helps to avoid race conditions by controlling access to shared resources.

Conclusion

The AutoResetEvent class is a powerful tool for synchronizing threads in C#. It allows for precise control over thread execution order, ensuring that tasks are performed in a coordinated manner. By understanding and utilizing AutoResetEvent, developers can create more reliable and efficient multithreaded applications.