Skip to content
async/await and Tasks
step 1/6

Reading — step 1 of 6

Learn

~2 min readConcurrency

Swift 5.5 added native async/await and structured concurrency. The model is similar to JavaScript and C#, but Swift's takes type-safety and cancellation seriously.

Basic async/await

func fetchUser(id: Int) async throws -> User {
    let url = URL(string: "https://api.example.com/users/\(id)")!
    let (data, _) = try await URLSession.shared.data(from: url)
    return try JSONDecoder().decode(User.self, from: data)
}

// Calling — must be in an async context:
let user = try await fetchUser(id: 42)

async marks a function as suspending — it can pause without blocking a thread. await is required at every call site to make suspension visible.

Tasks — top-level async work

Most code starts an async chain via a Task:

let task = Task {
    let user = try await fetchUser(id: 42)
    print(user.name)
}

// Cancel from outside:
task.cancel()

Parallel execution with async let

func fetchProfile(id: Int) async throws -> (User, [Post]) {
    async let user = fetchUser(id: id)
    async let posts = fetchPosts(userId: id)
    return try await (user, posts)        // both run in parallel
}

async let kicks off the work immediately. The await later collects the result. Total time = max(individual times), not sum.

TaskGroup — N parallel tasks

func fetchAll(ids: [Int]) async throws -> [User] {
    try await withThrowingTaskGroup(of: User.self) { group in
        for id in ids {
            group.addTask {
                try await fetchUser(id: id)
            }
        }
        var users: [User] = []
        for try await user in group {
            users.append(user)
        }
        return users
    }
}

Dynamic number of parallel tasks. Cancellation propagates: if you throw, all sibling tasks are cancelled.

Cancellation

Cancellation in Swift is cooperative — your code must check:

func longComputation() async throws -> Int {
    var result = 0
    for i in 0..<1_000_000 {
        try Task.checkCancellation()    // throws CancellationError if cancelled
        result += i
    }
    return result
}

Most standard library async APIs check cancellation automatically. Long-running custom loops should call Task.checkCancellation periodically.

Actors — thread-safe shared state

Swift 5.5 also added actors for protecting shared state:

actor BankAccount {
    private var balance: Int = 0
    
    func deposit(_ amount: Int) {
        balance += amount
    }
    
    func getBalance() -> Int {
        return balance
    }
}

let account = BankAccount()
await account.deposit(100)            // await — entering the actor
let b = await account.getBalance()    // serialized access

Actors serialize access to their state — only one task can be inside an actor's methods at a time. Race conditions on actor state become impossible.

Common mistakes

  • Calling async from sync without a Task — compile error. Wrap in Task { ... }.
  • Forgetting try for throwing async callstry await is the common combo.
  • Awaiting in a loop when independent items could run in parallel — use async let or TaskGroup.
  • Nested Task { } in actor methods unintentionally — the inner Task is detached from the actor's isolation. Use await instead when you want serialized access.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…