C# – Using ManualResetEventSlim and AutoResetEvent to signal waiting threads

In software development there are many ways to solve the same problem. It’s all about knowing what options are available and using the simplest approach for the job.

When you want one or more threads to wait until they are signaled before continuing, how do you do it? There are many options that you can use to accomplish this.

One of the simplest approaches is to use a ManualResetEventSlim or AutoResetEvent, like this:

static string deviceData = null; static ManualResetEventSlim gotDataSignal; static void Main(string[] args) { gotDataSignal = new ManualResetEventSlim(); while (true) { Console.WriteLine("Running device simulation loop."); Task.Run(DeviceSimulation); Console.WriteLine("Thread 1 waiting for gotDataSignal"); gotDataSignal.Wait(); Console.WriteLine($"Thread 1 signaled, got data {deviceData}"); Console.WriteLine("Resetting signal for next simulation"); gotDataSignal.Reset(); } } static void DeviceSimulation() { Console.WriteLine("Thread 2 - type anything to simulate getting device data"); deviceData = Console.ReadLine(); Console.WriteLine("Thread 2 signaling Thread 1 that it got data"); gotDataSignal.Set(); }
Code language: C# (cs)

Running this results in the following output:

Running device simulation loop. Thread 1 waiting for gotDataSignal Thread 2 - type anything to simulate getting device data 0001 1000 Thread 2 signaling Thread 1 that it got data Thread 1 signaled, got data 0001 1000 Resetting signal for next simulation Running device simulation loop. Thread 1 waiting for gotDataSignal Thread 2 - type anything to simulate getting device data f Thread 2 signaling Thread 1 that it got data Thread 1 signaled, got data f Resetting signal for next simulation Running device simulation loop. Thread 1 waiting for gotDataSignal Thread 2 - type anything to simulate getting device data
Code language: plaintext (plaintext)

You may have noticed that this is calling Reset(). Without calling this, the event wait handle remains in a signaled state and any threads that call Wait() will not block. This is where ManualResetEventSlim or AutoResetEvent get their bizarre names, and is the main difference between them. ManualResetEventSlim requires you to call Reset(), whereas AutoResetEvent automatically calls Reset() after Set() is called.

In the sections below, I’ll show examples that show the key difference between ManaulResetEventSlim (signals all threads at once) and AutoResetEvent (signals one thread at a time).

ManualResetEventSlim – Signal all waiting threads

ManualResetEventSlim is like waving a checkered flag at a car race. All race cars (waiting threads) line up at the starting line and wait for the checkered flag, and then they all start.

ManualResetEventSlim is simple to use. Create it, have threads call Wait(), and call Set() to let all threads through at once. As the name reveals, you must call Reset() to manually block all future waiting threads. Note: I’m not calling Reset() below, because this section’s main purpose is to show how ManualResetEventSlim signals all threads at once.

The following code shows this car race analogy in practice.

static void Main(string[] args) { Console.WriteLine("Welcome to the race track."); Console.WriteLine("Your job is to wave the checkered flag once all race cars are lined up"); Console.WriteLine("Press anything + enter to wave the flag"); using (var checkeredFlag = new ManualResetEventSlim()) { for (int i = 1; i <= 10; i++) { var raceCarNumber = i; //capture for closure Task.Run(() => { Console.WriteLine($"Race car {raceCarNumber} is ready"); checkeredFlag.Wait(); for(int j = 0; j < 100; j++) { //simulate laps around the track } Console.WriteLine($"Race car {raceCarNumber} finished"); }); } Console.ReadLine(); Console.WriteLine("Ready"); Console.WriteLine("Set"); Console.WriteLine("Go!"); checkeredFlag.Set(); Console.ReadLine(); } }
Code language: C# (cs)

Running this code produces the following output.

Welcome to the race track. Your job is to wave the checkered flag once all race cars are lined up Press anything + enter to wave the flag Race car 1 is ready Race car 7 is ready Race car 5 is ready Race car 6 is ready Race car 3 is ready Race car 4 is ready Race car 8 is ready Race car 2 is ready Race car 9 is ready Race car 10 is ready Start race Ready Set Go! Race car 9 finished Race car 3 finished Race car 2 finished Race car 4 finished Race car 10 finished Race car 1 finished Race car 7 finished Race car 6 finished Race car 5 finished Race car 8 finished
Code language: plaintext (plaintext)

As you can see, all cars (waiting threads) got signaled all at the same time.

AutoResetEvent – Signal one thread at a time

AutoResetEvent is like a store with one checkout lane. Only one customer (waiting thread) can be served at a time. The rest of the customers have to continue waiting.

AutoResetEvent is simple to use. Create it, have threads call WaitOne(), and call Set() to let through one thread at a time.

The following code shows this checkout lane analogy in practice.

static void Main(string[] args) { Console.WriteLine("Welcome to the store!"); Console.WriteLine("There's one checkout lane, so customers will have to queue up"); Console.WriteLine("Type anything to signify the next customer can be checked out"); using (var checkoutLaneCashier = new AutoResetEvent(initialState: false)) { for (int i = 1; i <= 5; i++) { var customerNumber = i; //capture for closure Task.Run(() => { Console.WriteLine($"Customer {customerNumber} is waiting in line"); checkoutLaneCashier.WaitOne(); Console.WriteLine($"Customer {customerNumber} is now checking out"); //simulate check out process Thread.Sleep(50); Console.WriteLine($"Customer {customerNumber} is done checking out"); }); } while (true) { Console.ReadLine(); Console.WriteLine("Serving next customer"); checkoutLaneCashier.Set(); } } }
Code language: C# (cs)

Running this code produces the following output.

Welcome to the store! There's one checkout lane, so customers will have to queue up Type anything to signify the next customer can be checked out Customer 2 is waiting in line Customer 5 is waiting in line Customer 4 is waiting in line Customer 1 is waiting in line Customer 3 is waiting in line next Serving next customer Customer 2 is now checking out Customer 2 is done checking out next Serving next customer Customer 5 is now checking out Customer 5 is done checking out next Serving next customer Customer 4 is now checking out Customer 4 is done checking out next Serving next customer Customer 1 is now checking out Customer 1 is done checking out next Serving next customer Customer 3 is now checking out Customer 3 is done checking out
Code language: plaintext (plaintext)

Compare this with ManualResetEventSlim. In this case, I had to keep typing something (I typed “next” every time) to make it call Set(), letting one customer through the checkout lane at a time.

Wait with timeout or cancellation token

It’s usually not a good idea to wait unconditionally. You should typically specify a timeout, pass in a cancellation token, or pass in a cancellation token with a timeout.

//wait with a timeout signal.Wait(TimeSpan.FromSeconds(5)); //wait with a cancel token signal.Wait(new CancellationTokenSource().Token); //wait with a cancel token with a timeout signal.Wait(new CancellationTokenSource(TimeSpan.FromSeconds(5)).Token);
Code language: C# (cs)

Which option you pick will depend on your specific scenario.

For example, let’s say your software is taking a payment and waiting for a customer to interact with a payment device. You may have a thread that is waiting for the payment data. The customer or cashier might want to cancel out of the transaction. In this case, you could call Cancel() on the cancellation token to stop the thread that’s waiting for the device data.

Leave a Comment