IAsyncResult
C#
Asynchronous Programming
Interface Implementation
.NET Framework

What is a proper implementation of the IAsyncResult interface?

Interview Questions practice on Codemia

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

Browse interview questions

Introduction

IAsyncResult belongs to the old .NET Asynchronous Programming Model, usually called APM or the Begin/End pattern. A proper implementation must expose completion state consistently, signal waiters correctly, invoke callbacks exactly once, and pair cleanly with a matching End method, but for new code you should strongly prefer Task and async APIs instead.

When You Should Still Care

Most modern .NET code should not invent new IAsyncResult implementations. The main valid reason is interoperability with an older API surface that requires methods such as:

  • 'BeginRead'
  • 'EndRead'
  • 'BeginInvoke'
  • 'EndInvoke'

If you are not integrating with that older contract, Task is the better design.

The Required Members

IAsyncResult requires:

  • 'AsyncState'
  • 'AsyncWaitHandle'
  • 'CompletedSynchronously'
  • 'IsCompleted'

A minimal implementation also needs internal result storage, exception storage, callback dispatch, and a way to signal completion.

A Minimal Example

csharp
1using System;
2using System.Threading;
3
4public sealed class SimpleAsyncResult<T> : IAsyncResult
5{
6    private readonly ManualResetEvent _waitHandle = new(false);
7    private readonly AsyncCallback? _callback;
8    private Exception? _exception;
9
10    public object? AsyncState { get; }
11    public WaitHandle AsyncWaitHandle => _waitHandle;
12    public bool CompletedSynchronously { get; private set; }
13    public bool IsCompleted { get; private set; }
14    public T? Result { get; private set; }
15
16    public SimpleAsyncResult(object? state, AsyncCallback? callback)
17    {
18        AsyncState = state;
19        _callback = callback;
20    }
21
22    public void Complete(T result, bool completedSynchronously)
23    {
24        Result = result;
25        CompletedSynchronously = completedSynchronously;
26        IsCompleted = true;
27        _waitHandle.Set();
28        _callback?.Invoke(this);
29    }
30
31    public void Fail(Exception ex, bool completedSynchronously)
32    {
33        _exception = ex;
34        CompletedSynchronously = completedSynchronously;
35        IsCompleted = true;
36        _waitHandle.Set();
37        _callback?.Invoke(this);
38    }
39
40    public T EndInvoke()
41    {
42        AsyncWaitHandle.WaitOne();
43        _waitHandle.Dispose();
44        if (_exception != null)
45            throw _exception;
46        return Result!;
47    }
48}

This is not meant to replace modern Task, but it shows the shape of a correct APM result object.

Pair It with Begin and End Methods

An IAsyncResult implementation only makes sense when the surrounding API follows the Begin/End pattern.

csharp
1using System;
2using System.Threading;
3
4public static class DemoService
5{
6    public static IAsyncResult BeginSquare(int value, AsyncCallback? callback, object? state)
7    {
8        var ar = new SimpleAsyncResult<int>(state, callback);
9
10        ThreadPool.QueueUserWorkItem(_ =>
11        {
12            try
13            {
14                ar.Complete(value * value, completedSynchronously: false);
15            }
16            catch (Exception ex)
17            {
18                ar.Fail(ex, completedSynchronously: false);
19            }
20        });
21
22        return ar;
23    }
24
25    public static int EndSquare(IAsyncResult asyncResult)
26    {
27        return ((SimpleAsyncResult<int>)asyncResult).EndInvoke();
28    }
29}

EndSquare is where callers block if needed, retrieve the result, and observe the stored exception.

CompletedSynchronously Must Be Honest

This property is not a performance hint you can fake. It tells the caller whether completion happened before the Begin method returned. If the operation actually finished later on a thread-pool thread, it must be false.

Incorrect values here can break callback logic in consumers that optimize for synchronous completion.

Dispose and Lifetime Concerns

If you expose a wait handle, you also need a consistent ownership story. In many implementations the End method is where the wait handle is observed and cleaned up. Forgetting that detail leads to leaked kernel handles.

That is one reason custom APM code is easy to get wrong.

Use Task for New Code

Modern equivalent:

csharp
1using System.Threading.Tasks;
2
3public static Task<int> SquareAsync(int value)
4{
5    return Task.Run(() => value * value);
6}

This is shorter, safer, and much easier to compose than a custom IAsyncResult type.

Common Pitfalls

The biggest mistake is implementing IAsyncResult for new APIs when Task would be simpler and safer.

Another issue is forgetting that the Begin/End pair is the real contract. An IAsyncResult object by itself is not enough.

A third problem is setting CompletedSynchronously incorrectly or failing to signal the wait handle before invoking the callback.

Summary

  • A proper IAsyncResult implementation must track state, completion, wait signaling, and exceptions consistently.
  • It should be paired with matching Begin and End methods.
  • 'CompletedSynchronously must reflect reality, not guesswork.'
  • Resource cleanup matters because AsyncWaitHandle has lifetime costs.
  • For new code, prefer Task and async instead of creating custom APM infrastructure.

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.