Sobes.tech
Senior

How are async and await mechanisms implemented under the hood?

sobes.tech AI

Answer from AI

Async/await in Swift is built on structured concurrency and uses Dispatch/Global Actors. Under the hood, the following mechanisms operate:

  1. Continuation Style: Asynchronous functions are compiled into a continuation style. Instead of returning a value immediately, control is transferred to the Swift runtime, which saves the current state of the function (local variables, execution pointer). When the asynchronous operation completes, the runtime resumes the function from the saved state.

  2. Task: Each call to an async function creates or joins a structured Task. A Task represents a unit of work and forms a hierarchy. The parent Task waits for the completion of its child Tasks.

  3. Job: A Task is broken down into smaller units of work called Jobs. A Job represents a fragment of code that can be executed on an executor.

  4. Executor: The executor is responsible for running Jobs. The standard library uses global DispatchQueues as executors for most asynchronous tasks. Specific contexts (e.g., MainActor) have their own specialized executors.

  5. Suspension Points: await is a suspension point. When reaching await, the function suspends, control is transferred to the caller or executor, and the current Job is completed. The Swift runtime saves the function's state.

  6. Resumption: When the awaited asynchronous operation completes, the Swift runtime creates a new Job to continue executing the suspended function. This Job is queued on an executor.

  7. Cancellation: Structured concurrency supports hierarchical cancellation. Cancelling a parent Task automatically cancels all its child Tasks. Asynchronous operations can check the cancellation status and react accordingly.

Here is a simplified example of compiling an async function:

// Original async function
func fetchData() async -> Data {
    // Asynchronous operation
    let data = await URLSession.shared.data(from: url).0
    return data
}

// Simplified compilation representation
// Instead of returning, the function takes a Continuation
func fetchData(_ continuation: Continuation<Data, Error>) {
    // Initial code of the function
    let url = URL(string: "...")! // Example
    
    // Start asynchronous operation
    URLSession.shared.data(from: url) { data, _, error in
        // This closure executes after the async operation completes
        if let data = data {
            // Pass the result to the continuation, resuming the function
            continuation.resume(returning: data)
        } else if let error = error {
            continuation.resume(throwing: error)
        } else {
            // Handle other cases
            continuation.resume(throwing: NSError(domain: "", code: 0, userInfo: nil))
        }
    }
    // The function ends here, waiting for resume to be called by the continuation
}

Await is essentially syntactic sugar over Continuation management and Job scheduling on the executor. Suspension does not block the thread directly but allows the executor to run other Jobs.

How are async and await mechanisms implemented under… - sobes.tech