C#
multithreading
ManualResetEvent
programming
synchronization

How to find what state ManualResetEvent is in?

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

ManualResetEvent has a signaled or unsignaled state, but it does not expose that state through a simple public property. That design is intentional, because reading synchronization state as if it were ordinary data is often race-prone and leads to incorrect thread coordination.

What ManualResetEvent Actually Does

ManualResetEvent is a kernel-based synchronization primitive. When it is signaled, waiting threads can continue. When it is unsignaled, waiting threads block until another thread calls Set().

csharp
1using System;
2using System.Threading;
3
4var gate = new ManualResetEvent(false);
5
6Thread worker = new Thread(() =>
7{
8    Console.WriteLine("worker waiting");
9    gate.WaitOne();
10    Console.WriteLine("worker resumed");
11});
12
13worker.Start();
14
15Thread.Sleep(500);
16gate.Set();
17worker.Join();

The state exists, but the type is designed around waiting and signaling, not around querying a status flag.

There Is No Reliable "State Property"

Many developers want something like gate.IsSet. ManualResetEvent does not provide it.

That is not an omission by accident. If one thread reads a state property and then acts on it later, another thread may change the event in between. In other words, the read is stale as soon as it happens.

That is why the better question is usually:

  • "Should I wait?"
  • "Can I probe without blocking?"
  • "Should I track state separately?"

Those are different design problems.

Non-Blocking Probe with WaitOne(0)

If you need to check whether the event is currently signaled without blocking, use a zero-timeout wait.

csharp
1using System;
2using System.Threading;
3
4var gate = new ManualResetEvent(true);
5
6bool isSignaledNow = gate.WaitOne(0);
7Console.WriteLine(isSignaledNow);

This tells you whether the event was signaled at that instant. It does not make future assumptions safe.

That is the closest thing to "check the current state," but treat it as a probe, not as a durable truth.

Why Polling State Is Often the Wrong Design

Suppose you write:

csharp
1if (gate.WaitOne(0))
2{
3    // assume safe to proceed
4}

Another thread may call Reset() immediately afterward. If your logic depends on the state remaining stable, the design is flawed.

Synchronization primitives are most useful when they are used directly for coordination, not when code tries to mirror them into a status variable and then reason from there.

If a thread should proceed only when the event is signaled, just wait.

csharp
gate.WaitOne();

That is much safer than "checking first, then doing something later based on the check."

Track State Separately Only If You Own the Protocol

Sometimes you genuinely need a readable state for diagnostics or UI. In that case, track your own flag alongside the event, and update both in one controlled place.

csharp
1using System.Threading;
2
3public sealed class SignalGate
4{
5    private readonly ManualResetEvent _event = new(false);
6    private int _isOpen;
7
8    public bool IsOpen => Volatile.Read(ref _isOpen) == 1;
9
10    public void Open()
11    {
12        Volatile.Write(ref _isOpen, 1);
13        _event.Set();
14    }
15
16    public void Close()
17    {
18        Volatile.Write(ref _isOpen, 0);
19        _event.Reset();
20    }
21
22    public void Wait() => _event.WaitOne();
23}

This does not change the race semantics of multithreaded code, but it can provide useful observability when you control the full protocol.

Consider ManualResetEventSlim

If you are working entirely inside managed code and need a lighter-weight primitive, ManualResetEventSlim can be a better fit. It also supports non-blocking checks through zero-timeout waits.

csharp
1using System;
2using System.Threading;
3
4var gate = new ManualResetEventSlim(false);
5
6Console.WriteLine(gate.Wait(0)); // false
7gate.Set();
8Console.WriteLine(gate.Wait(0)); // true

This still does not solve the core race issue, but it is often the more appropriate primitive in application-level code.

Use the Right Abstraction

If you are only trying to notify one task that another task finished, a TaskCompletionSource or SemaphoreSlim may express the intent more clearly than ManualResetEvent.

The more modern your async code is, the less often you need to reach for kernel-backed event handles directly.

Common Pitfalls

  • Looking for a permanent IsSet property on ManualResetEvent.
  • Treating WaitOne(0) as a stable guarantee instead of a momentary probe.
  • Polling event state instead of using wait-based coordination.
  • Mirroring synchronization state into unsynchronized ordinary fields.
  • Using ManualResetEvent when a higher-level primitive would express the intent better.

Summary

  • 'ManualResetEvent does not expose a built-in state property.'
  • Use WaitOne(0) only when you need a non-blocking instantaneous probe.
  • Prefer waiting directly instead of checking and then acting later.
  • Track separate readable state only if you own the entire signaling protocol.
  • Consider ManualResetEventSlim or higher-level abstractions for newer code.

Related reading
Course
Intermediate
27 lessons
14 hours
OOD Fundamentals

Master object-oriented design from first principles, SOLID, design patterns, and classic interview problems with hands-on coding.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.