A separate task: Task
A cashier in the café with buzzers cannot step aside to a table and wait for a signal: there is a line behind them. Yet they do have errands that involve waiting. For example, the milk has run out: they need to order more from the supplier and wait for the delivery.
So the cashier writes the errand on a slip of paper and sticks the slip on the spike by the counter. They keep serving the line, and they will get to the slip in a free minute, when nobody is at the counter.
An ordinary function in a program is in the same spot: you cannot write await in it, yet sometimes you need to wait from there too. For example, in an app an ordinary function responds to a button tap, and it has to load data from a server.
Tasks and Task
That is what Task is for. A task is work that Swift runs separately from the rest of the code, like the errand on the slip. Task { … } creates a task, and inside its braces await is allowed, even when the code around it is ordinary.
You have already met the type Task: Task.sleep is its type method. And Task { … } calls the initializer of Task and passes it a closure with the work. There are no other arguments, so the closure comes right after the type name and the parentheses are dropped.
let task = Task {
print("inside") // inside
}
await task.value
print("end") // endLet's go line by line:
Task { … }creates a task and returns it at once. The code in the braces runs separately, and the lines afterTaskgo on without waiting for it.- The constant
tasknow holds the task itself. await task.valuewaits until the task ends. Meanwhile the task runs and printsinside.print("end")printsend.
When the task runs
A task started in main.swift runs in the same main part of the program as the file's lines. Like the slip on the spike, it gets its turn only when the file's lines stop at await. In the example that is await task.value: the lines wait, and in the freed time the task prints inside.
In the example the task sits right in main.swift, where await is allowed anyway: that makes the order of the lines easier to follow. In an ordinary function you write Task { … } the same way.
Here the slip picture stops working. A slip waits until the cashier has a free minute. But Swift may run a task started inside a function at the same time as the rest of the code, without waiting for any await.
The task's result
A task can have a result, and value is that result. The task in the first example has none, so there await task.value is needed only for the waiting. This task, though, makes coffee and returns a string:
func makeCoffee() async throws -> String {
try await Task.sleep(for: .milliseconds(100))
return "coffee"
}
let order = Task {
try await makeCoffee()
}
print("order") // order
let drink = try await order.value
print(drink) // coffeeLet's see what happens:
- The closure's body is a single expression, so the task returns the string from
makeCoffeewithoutreturn. - The task is created and waits for its turn, while
print("order")printsorder. try await order.valuewaits for the task to end and hands back its result.makeCoffeemay throw an error, andvaluepasses it on, sotryis needed.drinkgets the string"coffee", andprint(drink)printscoffee.
Common mistake
A task was started but nobody waited for it:
Task { // ✗
print("inside")
}
print("end")The program prints only end, on every run. The file's lines never stop at await, so the task never gets its turn. After print("end") the program ends without waiting for tasks, and the code in the braces never runs. The café has closed, and the slip is still on the spike.
In an app that keeps running, the task will run, but here you have to wait for it with await task.value, as in the example above.
Key points
Task { … }creates a task, work that Swift runs separately from the rest of the code; inside its bracesawaitis allowed.- A task in
main.swiftgets its turn only when the file's lines stop atawait; the program itself does not wait for the task. await task.valuewaits for the task to end and hands back its result; if the task's code may throw an error, you writetry await.