Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.syntblaze.com/llms.txt

Use this file to discover all available pages before exploring further.

The await operator suspends the evaluation of the enclosing asynchronous method or top-level statement until the asynchronous operation represented by its operand completes. When the operation finishes, the await operator returns the result of the operation (if any) and resumes the execution of the context from the suspension point.

Syntax and Context

The await operator can be used inside a method, lambda expression, or anonymous function that is explicitly modified by the async keyword. Since C# 9.0, await can also be used directly in top-level statements without any explicit async modifier. The operator is subject to strict context restrictions. Most notably, await cannot be used inside the body of a lock statement, which results in compiler error CS1996.
using System.Threading.Tasks;

public class AwaitSyntaxExample
{
    private readonly object _syncRoot = new object();

    public async Task ExecuteAsync()
    {
        // Syntax visualization
        string result = await AsyncOperationReturningTaskOfTResult();
        await AsyncOperationReturningTask();

        // Invalid context example (CS1996)
        lock (_syncRoot) 
        {
            // await AsyncOperationReturningTask(); // Uncommenting causes compiler error CS1996
        }
    }

    private Task<string> AsyncOperationReturningTaskOfTResult()
    {
        return Task.FromResult("Success");
    }

    private Task AsyncOperationReturningTask()
    {
        return Task.CompletedTask;
    }
}

Execution Mechanics

When the C# compiler encounters an await expression, it transforms the enclosing context into a state machine. The mechanical flow is as follows:
  1. Evaluation: The operator evaluates the operand to obtain an awaitable object.
  2. Completion Check: It checks if the asynchronous operation has already completed. If true, execution continues synchronously without suspension.
  3. Suspension: If the operation is pending, await suspends the execution, captures the current execution context (such as a SynchronizationContext or TaskScheduler), and yields control back to the caller.
  4. Continuation: Once the asynchronous operation completes, the state machine resumes execution on the captured context (unless explicitly bypassed via ConfigureAwait(false)), and the await operator extracts the result.

The Awaitable Pattern

The await operator is not strictly bound to Task or Task<TResult>. It can be applied to any expression that satisfies the awaitable pattern. An object is awaitable if it exposes a GetAwaiter() method—which can be implemented as an instance method or an extension method—that returns an awaiter instance satisfying the following criteria:
  • Implements the System.Runtime.CompilerServices.INotifyCompletion or ICriticalNotifyCompletion interface.
  • Possesses a boolean IsCompleted property.
  • Possesses a GetResult() method.
Allowing GetAwaiter() to be an extension method is a fundamental aspect of the pattern, as it enables developers to make types awaitable that they do not own or cannot modify. The following compilable code demonstrates the manual equivalent of the code the compiler generates for an await expression:
using System;
using System.Threading.Tasks;

public class AwaiterPatternExample
{
    public void ManualAwaitExecution()
    {
        Task<int> operand = Task.Run(() => 42);
        
        // 1. Obtain the awaiter
        var awaiter = operand.GetAwaiter(); 

        // 2. Completion Check
        if (!awaiter.IsCompleted)
        {
            // 3. Suspend execution and schedule continuation
            awaiter.OnCompleted(() => 
            {
                // 4. Continuation and Resume point
                int result = awaiter.GetResult();
                Console.WriteLine($"Resumed with result: {result}");
            });
        }
        else
        {
            // Synchronous completion path
            int result = awaiter.GetResult();
            Console.WriteLine($"Completed synchronously with result: {result}");
        }
    }
}

Type Unwrapping

The type returned by the await expression is determined by the return type of the awaiter’s GetResult() method:
  • If the operand is Task<TResult> or ValueTask<TResult>, the await expression evaluates to TResult.
  • If the operand is Task or ValueTask, the await expression evaluates to void.

Exception Handling

When an awaited asynchronous operation faults, the underlying task typically encapsulates the exception within an AggregateException. The await operator intercepts this state, unwraps the AggregateException, and rethrows the first inner exception directly. It utilizes System.Runtime.ExceptionServices.ExceptionDispatchInfo to preserve the original stack trace of the exception from the point of failure to the await expression.
Master C# with Deep Grasping Methodology!Learn More