Advanced Concurrency: async let, TaskGroup & Actors
Go beyond a single await . Run work in parallel with async let and withTaskGroup , protect shared state with actors , pin UI work to @MainActor , and let the compiler guard you with Sendable .
Learn Advanced Concurrency: async let, TaskGroup & Actors in our free Swift course — a beginner-friendly interactive lesson with worked examples, a practice…
Part of the free Swift course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
What You'll Learn in This Lesson
1️⃣ async let — Parallel Work
Each async let launches a child task right away. The tasks run side by side; you join the results with await only when you actually use them.
2️⃣ withTaskGroup — Many Tasks
When the number of tasks isn't fixed, a task group lets you addTask in a loop and gather results with for await as each finishes.
3️⃣ Actors — Safe Shared State
An actor serializes access to its state so only one task touches it at a time — no data races. Code inside the actor is synchronous; code outside must await . Use @MainActor to pin UI work to the main thread, and Sendable to mark types safe to share.
Now you try. Add the keyword cross-actor calls need.
📋 Quick Reference
Fetch two simulated sizes in parallel and sum them, then try the task-group version. Compare with the comments.
Practice quiz
What does 'async let' enable?
- Sequential awaiting
- Starting child tasks that run in parallel, awaited later
- Cancelling tasks
- Locking a property
Answer: Starting child tasks that run in parallel, awaited later. async let starts a child task immediately and lets you await its result later, enabling parallelism.
When do you await an async let binding?
- Never
- Before declaring it
- At the point you actually use its value
- Only inside an actor
Answer: At the point you actually use its value. You await the async let when you need its result, allowing the work to run concurrently until then.
What is withTaskGroup used for?
- Running a dynamic number of child tasks concurrently
- Defining a struct
- Storing data
- Animating views
Answer: Running a dynamic number of child tasks concurrently. A task group spawns a variable number of concurrent child tasks and collects their results.
What problem do actors solve?
- Slow networking
- Parsing JSON
- Drawing UI
- Protecting mutable state from data races
Answer: Protecting mutable state from data races. Actors serialize access to their mutable state, preventing concurrent data races.
How do you usually access an actor's properties from outside?
- Directly and synchronously
- With await, because access is asynchronous
- Through a global lock
- You cannot access them
Answer: With await, because access is asynchronous. Cross-actor access is asynchronous, so it requires await.
What does @MainActor guarantee for a function or type?
- It runs on a background thread
- It cannot be awaited
- Its code runs on the main thread
- It disables concurrency
Answer: Its code runs on the main thread. @MainActor isolates code to the main thread, ideal for UI updates.
What does the Sendable protocol indicate?
- A type can be encoded to JSON
- A type is a view
- A type is an actor
- A type is safe to pass across concurrency boundaries
Answer: A type is safe to pass across concurrency boundaries. Sendable marks types that can be safely shared between concurrent tasks.
Inside its own methods, an actor accesses its state...
- Synchronously, without await
- With await every time
- Only via closures
- Never
Answer: Synchronously, without await. Code already running on the actor accesses its own isolated state synchronously.
Child tasks created with async let are cancelled if...
- They never start
- The enclosing scope exits before they're awaited
- You call await
- The actor is deallocated
Answer: The enclosing scope exits before they're awaited. Structured child tasks are cancelled when their scope ends without awaiting them.
A key benefit of structured concurrency is...
- Manual thread management
- Avoiding async entirely
- Child task lifetimes are tied to a scope, simplifying cleanup
- Removing the need for actors
Answer: Child task lifetimes are tied to a scope, simplifying cleanup. Structured concurrency ties task lifetimes to scopes, making cancellation and cleanup predictable.