Wrapping ManualResetEvent as awaitable task
Interview Questions practice on Codemia
Over 8,000 real interview questions from top companies, searchable by company and role.
Introduction
ManualResetEvent belongs to the older blocking side of .NET concurrency, while await belongs to the task-based asynchronous model. If a legacy API exposes a ManualResetEvent, you often need a bridge that lets async code wait for it without parking a thread the whole time.
The important point is that "awaitable" does not mean "run WaitOne() inside Task.Run." That works, but it defeats much of the reason to use async code in the first place.
Why Task.Run(() => WaitOne()) Is A Weak Bridge
A first attempt often looks like this:
Functionally, that completes when the event is signaled. Operationally, it burns a thread-pool thread for the entire wait. If the event is signaled quickly, the waste is small. If it stays unsignaled for seconds or minutes, or if many waits exist at once, the thread-pool cost becomes real.
That is why a registration-based bridge is better. You want the runtime to observe the wait handle and complete a Task when the signal arrives, not dedicate a worker thread to sleep inside WaitOne().
Use ThreadPool.RegisterWaitForSingleObject
The standard pattern is ThreadPool.RegisterWaitForSingleObject plus TaskCompletionSource. The registration asks the runtime to invoke a callback when the wait handle is signaled.
That code gives the caller a proper Task to await and avoids blocking a thread for the whole wait duration.
Use The Wrapper From Async Code
Once the extension exists, using it looks natural:
Because ManualResetEvent stays signaled until someone calls Reset(), multiple awaiters can pass after Set() if you use it that way. That is different from AutoResetEvent, where one signal normally releases one waiter.
Add Cancellation And Lifetime Rules
If the signal might never arrive, cancellation is essential. Otherwise the awaited task can remain incomplete forever.
You also need a clear lifetime rule for the underlying wait handle. If some other code disposes the ManualResetEvent while the async wait is still active, the behavior becomes hard to reason about. In practice, the owner of the event should ensure the handle outlives all pending waits or that pending waits are canceled before disposal.
Consider An Async-Native Alternative
If you control the design, it is often better to stop exposing ManualResetEvent at all. An async-native primitive such as an AsyncManualResetEvent built on TaskCompletionSource is usually easier to compose in modern code.
This avoids bridging two concurrency models and usually produces cleaner application code.
Common Pitfalls
One common mistake is assuming Task.Run makes a blocking wait "async enough." It does not. Another is forgetting to unregister the wait callback after completion or cancellation, which can leave registrations hanging around longer than intended. Developers also sometimes treat ManualResetEvent like AutoResetEvent and get surprising behavior when many awaiters all pass after one signal. Finally, if you truly own the API, exposing a blocking primitive to async callers is often the design smell that should be fixed instead of wrapped.
Summary
- '
ManualResetEventis blocking, so a good async wrapper should avoid a dedicated blocked thread.' - '
ThreadPool.RegisterWaitForSingleObjectplusTaskCompletionSourceis the usual bridge.' - Add cancellation so async callers can stop waiting predictably.
- Be explicit about lifetime and disposal of the underlying wait handle.
- If you control the design, prefer an async-native reset event instead of wrapping a legacy primitive.
Related reading
- Write a well designed async / non-async API
- Writing a thread safe modular counter in Java
- Writing an asynchronous process that can be awaited
- Writing and Reading file async
- Wrapping StopWatch timing with a delegate or lambda?
- Write to Windows Application Event Log without event source registration
- writing tfrecord with multithreading is not fast as expected
- Writing to a TextBox from another thread?

OOD Fundamentals
Master object-oriented design from first principles, SOLID, design patterns, and classic interview problems with hands-on coding.
View the courseTrack 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.