The async/await pattern was introduced to simplify asynchronous programming by making asynchronous code look and behave more like synchronous code. Both C# and Python support asynchronous programming using await, but their implementation details differ significantly due to differences in language design, execution model, and runtime.


Implementation of await in C#

How C# await Works

C# compiles async methods into a state machine. The execution flow:

  1. Executes until await is reached.
  2. If the awaited task is not completed, it suspends execution and returns control to the caller.
  3. When the task completes, execution resumes from where it left off.

Example

using System;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        Console.WriteLine("Start");
        await DoWorkAsync();
        Console.WriteLine("End");
    }

    static async Task DoWorkAsync()
    {
        Console.WriteLine("Working...");
        await Task.Delay(2000);  // Non-blocking delay
        Console.WriteLine("Done!");
    }
}

How C# Compiles await (State Machine Transformation)

The compiler transforms the async method into a state machine, similar to:

class DoWorkAsyncStateMachine : IAsyncStateMachine
{
    public int state;
    public AsyncTaskMethodBuilder builder;
    public TaskAwaiter awaiter;

    public void MoveNext()
    {
        try
        {
            if (state == 0)
            {
                Console.WriteLine("Working...");
                awaiter = Task.Delay(2000).GetAwaiter();

                if (!awaiter.IsCompleted)
                {
                    state = 1;
                    builder.AwaitUnsafeOnCompleted(ref awaiter, ref this);
                    return;
                }
            }

            if (state == 1)
            {
                awaiter.GetResult();  // Resume execution
                Console.WriteLine("Done!");
            }
        }
        catch (Exception ex)
        {
            builder.SetException(ex);
            return;
        }

        builder.SetResult();  // Task completed
    }

    public void SetStateMachine(IAsyncStateMachine stateMachine) { }
}

Key C# Mechanisms

  • State Machine (IAsyncStateMachine): Controls execution flow.
  • Task Awaiter (TaskAwaiter): Registers a continuation to resume execution.
  • Continuation (AwaitUnsafeOnCompleted): Resumes execution when the awaited task completes.
  • Exception Handling (SetException): Propagates exceptions.

Implementation of await in Python

How Python await Works

Python uses coroutines and an event loop for async execution. The await keyword:

  1. Pauses execution of the coroutine at an async operation.
  2. Returns control to the event loop.
  3. Resumes execution when the awaited task completes.

Example

import asyncio

async def do_work():
    print("Working...")
    await asyncio.sleep(2)  # Non-blocking delay
    print("Done!")

async def main():
    print("Start")
    await do_work()
    print("End")

asyncio.run(main())

How Python Executes await (Coroutine-based)

Under the hood, async functions in Python are coroutines implemented using generators (yield from).

The Python equivalent of the state machine transformation is done via coroutine objects:

import asyncio

async def async_function():
    print("Step 1")
    await asyncio.sleep(1)  # Suspends execution
    print("Step 2")

coro = async_function()
print(coro)  # <coroutine object async_function at 0x...>

# Manually drive coroutine execution
loop = asyncio.get_event_loop()
loop.run_until_complete(coro)  # Runs coroutine to completion

Key Python Mechanisms

  • Coroutines (async def): Functions that return a coroutine object instead of executing immediately.
  • Event Loop (asyncio.run): Runs coroutines in an asynchronous manner.
  • Task Scheduling (asyncio.create_task): Allows multiple coroutines to run concurrently.
  • Coroutine Suspension (yield from): Similar to how await works in C#.

Side-by-Side Comparison of Await Implementation

Feature C# (async/await) Python (async/await)
Execution Model State Machine (IAsyncStateMachine) Coroutine-based (asyncio)
Primary Mechanism TaskAwaiter & MoveNext() Coroutines & Event Loop
Control Flow Resumes using continuations Resumes using event loop
Concurrency Model Thread-pool-based (Task.Run) Single-threaded event loop (asyncio)
Parallel Execution Yes (Task.Run) Limited (only one thread, but can use asyncio.to_thread)
Suspension Handling AwaitUnsafeOnCompleted() yield from (under the hood)
Optimizations ValueTask<T> (avoids allocations) asyncio.create_task() (background scheduling)

Both languages implement await efficiently, but C# optimizes for multi-threading, while Python focuses on cooperative multitasking using an event loop.