C# – ManualResetEventSlim and AutoResetEvent

When you want thread(s) to wait until they’re signaled before continuing, there are two simple options:

  • ManualResetEventSlim: Signals all waiting threads at once.
  • AutoResetEvent: Signals one waiting thread at time.

I’ll show examples of using both of these.

ManualResetEventSlim examples

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

To use ManualResetEventSlim:

  • Create a ManualResetEventSlim object.
  • In threads that need to wait, call Wait().
  • In another thread, call Set() to signal all waiting threads.

Here’s an example of using ManualResetEventSlim:

using System.Threading.Tasks;
using System.Threading;

Console.WriteLine("Welcome to the race track.");
Console.WriteLine("Your job is to wave the flag once all race cars are lined up");
Console.WriteLine("Press anything + enter to wave the flag");

using var startRaceFlag = new ManualResetEventSlim();

for (int i = 1; i <= 4; i++)
{
    var raceCarNumber = i; //capture for closure
    Task.Run(() =>
    {
        Console.WriteLine($"Race car {raceCarNumber} is ready");
        startRaceFlag.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!");

startRaceFlag.Set();

Console.ReadLine();
Code language: C# (cs)

This outputs the following:

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 4 is ready
Race car 1 is ready
Race car 2 is ready
Race car 3 is ready
Start race
Ready
Set
Go!
Race car 3 finished
Race car 4 finished
Race car 2 finished
Race car 1 finishedCode language: plaintext (plaintext)

Example of using ManualResetEventSlim.Reset()

Calling Set() puts the ManualResetEventSlim in a signaled state. This signals all waiting threads and lets them through. To put it back into an unsignaled state, call Reset().

Here’s an example:

using System.Threading;
using System.Threading.Tasks;

string deviceData = null;
var manualResetEventSlim  = new ManualResetEventSlim();

while (true)
{
    Console.WriteLine("Running device simulation loop.");

    Task.Run(DeviceSimulation);

    Console.WriteLine("Thread 1 waiting for manualResetEventSlim");
    manualResetEventSlim.Wait();

    Console.WriteLine($"Thread 1 signaled, got data {deviceData}");
    Console.WriteLine("Resetting signal for next simulation");

    manualResetEventSlim.Reset();

    Console.WriteLine();

}
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");
    manualResetEventSlim.Set();
}
Code language: C# (cs)

Running this results in the following output:

Running device simulation loop.
Thread 1 waiting for manualResetEventSlim
Thread 2 - type anything to simulate getting device data
hello world
Thread 2 signaling Thread 1 that it got data
Thread 1 signaled, got data hello world
Resetting signal for next simulation

Running device simulation loop.
Thread 1 waiting for manualResetEventSlim
Thread 2 - type anything to simulate getting device dataCode language: plaintext (plaintext)

AutoResetEvent example

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

To use AutoResetEvent:

  • Create an AutoResetEvent object.
  • In threads that need to wait, call WaitOne().
  • In another thread, call Set() to signal the next waiting thread.

Here’s an example:

using System.Threading.Tasks;
using System.Threading;

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 <= 3; 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)

Note: It’s a very common mistake to pass in the loop variable to the Task.Run() lambda. Save the loop variable to a local variable instead and pass it in. This is necessary because closures capture variables, not values.

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 1 is waiting in line
Customer 3 is waiting in line
Customer 2 is waiting in line
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
next!
Serving next customer
Customer 2 is now checking out
Customer 2 is done checking out
Code language: plaintext (plaintext)

Notice that calling Set() only let one customer (thread) through at a time. It should be noted that AutoResetEvent is very similar to using a SemaphoreSlim for throttling threads.

Wait with timeout or CancellationToken

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.