Skip to content
Theory · Asynchronous code · lesson 3 of 7

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.

SWIFT
let task = Task {
    print("inside") // inside
}
await task.value
print("end") // end

Let's go line by line:

  1. Task { … } creates a task and returns it at once. The code in the braces runs separately, and the lines after Task go on without waiting for it.
  2. The constant task now holds the task itself.
  3. await task.value waits until the task ends. Meanwhile the task runs and prints inside.
  4. print("end") prints end.

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:

SWIFT
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) // coffee

Let's see what happens:

  1. The closure's body is a single expression, so the task returns the string from makeCoffee without return.
  2. The task is created and waits for its turn, while print("order") prints order.
  3. try await order.value waits for the task to end and hands back its result. makeCoffee may throw an error, and value passes it on, so try is needed.
  4. drink gets the string "coffee", and print(drink) prints coffee.

Common mistake

A task was started but nobody waited for it:

SWIFT
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 braces await is allowed.
  • A task in main.swift gets its turn only when the file's lines stop at await; the program itself does not wait for the task.
  • await task.value waits for the task to end and hands back its result; if the task's code may throw an error, you write try await.