In what order do these prints appear when a main-actor function creates a Task, yields, and starts a detached task?
Predict first
What is the order of the prints?
@MainActor func demo() async {
print("1 before")
Task { print("3 task body") }
print("2 after creating the task")
await Task.yield()
print("4 after yield")
let t = Task.detached { print("5 detached body, off the main actor") }
await t.value
print("6 end")
}
await demo()
- 1, 2, 3, 4, 5, 6
- 1, 3, 2, 4, 5, 6
- 1, 2, 4, 3, 5, 6
Mental model. You leave a note for a colleague who is on the phone. They will read it when the call pauses (the suspension point), not while you are still talking.
Task { … } started from the main actor inherits the main actor’s isolation, so its body must run on the main actor, and the main actor is busy running demo() until that function reaches a suspension point. So “2 after creating the task” prints before the task body even though the task was created first. await Task.yield() is the first suspension: it lets other main-actor work run, and the queued task body (“3”) gets its turn before “4”. Task.detached does not inherit the actor, so its body runs elsewhere; the function waits for it with await t.value, and “6” comes last because the awaiting is what orders them.
Know what is promised and what is only typical. Guaranteed: a task does not start running its body inside the code that created it; await on a task’s value orders what follows it after that task has finished; and actor-isolated code never runs concurrently with other code on the same actor. Not guaranteed: the relative order of two unstructured tasks you create back to back, or exactly when the runtime schedules a task relative to a yield. On the main actor this is usually first in first out, but write code that does not depend on it (await the tasks, or use a task group or an AsyncStream when order matters).
The task body waits for the main actor to be free; the yield frees it, and the detached task is awaited before the last print.
Common traps
- Expecting the new Task’s body to run before the next line. It runs when the current code suspends or finishes.
- Assuming
Task.detachedalso runs on the main actor. It inherits nothing: no actor, no priority, no task-locals.
They might follow up with
- What if
demo()never suspended? The task would run after it returned to the run loop, still on the main actor. - How do you guarantee order of several jobs? Await them one at a time, or feed one consumer from a stream (Q300).
Related questions
- 27.4.1 Two tasks each withdraw 80 from an actor account holding 100, with an await between the balance check and the…
- 27.3.1 Blocks are scheduled on the main queue from the main thread, and one of them schedules another. In what order…
- 27.4.4 What is a reliable routine for answering “what does this print?” under interview pressure?
- 10.3.1 What does async/await actually do, and what is a suspension point?
- 10.3.2 How do tasks form a tree: Task, task groups, async let and detached tasks, and what does a child inherit?
- 10.6.2 How do you bridge repeated events, GCD and Combine into async code with AsyncStream?
← Two tasks each withdraw 80 from an actor account holding 100, with an… Rows keep a @State value. After removing the first of three items… →