Отдельная задача: Task
Кассир в кафе с пейджерами не может отойти к столику и ждать сигнала: за ним очередь. А дела с ожиданием у него бывают. Например, кончилось молоко: нужно заказать его у поставщика и дождаться доставки.
Тогда кассир пишет поручение на листке и накалывает листок на штырь у кассы. Сам он обслуживает очередь дальше, а за листок возьмётся в свободную минуту, когда у кассы никого не будет.
С обычной функцией в программе то же самое: await в ней писать нельзя, а ждать иногда нужно и оттуда. Например, в приложении на нажатие кнопки отвечает обычная функция, а ей нужно загрузить данные с сервера.
Задачи и Task
Для этого есть Task. Задача — работа, которую Swift выполняет отдельно от остального кода, как поручение на листке. Task { … } создаёт задачу, и внутри её фигурных скобок await разрешён, даже если вокруг обычный код.
С типом Task вы уже встречались: Task.sleep — его метод типа. А запись Task { … } вызывает инициализатор Task и передаёт ему замыкание с работой. Других аргументов нет, поэтому замыкание стоит сразу после имени типа, а круглые скобки опущены.
let task = Task {
print("inside") // inside
}
await task.value
print("end") // endРазберём по строкам:
Task { … }создаёт задачу и сразу возвращает её. Код в скобках выполняется отдельно, а строки послеTaskидут дальше, не дожидаясь его.- В константе
taskтеперь лежит сама задача. await task.valueждёт, пока задача закончится. За это время задача выполняется и печатаетinside.print("end")печатаетend.
Когда задача выполняется
Задача, запущенная в main.swift, выполняется в той же главной части программы, что и строки файла. Как листок на штыре, она получает очередь, только когда строки файла остановятся на await. В примере это await task.value: строки ждут, а в освободившееся время задача печатает inside.
В примере задача стоит прямо в main.swift, где await и так разрешён: так проще проследить, в каком порядке идут строки. В обычной функции Task { … } пишут так же.
Здесь картинка с листком неточна. Листок ждёт, пока у кассира выдастся свободная минута. А задачу, запущенную внутри функции, Swift может выполнить одновременно с остальным кодом, не дожидаясь никакого await.
Результат задачи
У задачи бывает результат, и value — это он. У задачи из первого примера результата нет, поэтому await task.value там нужен ради ожидания. А эта задача готовит кофе и возвращает строку:
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Разберём, что происходит:
- Тело замыкания — одно выражение, поэтому задача возвращает строку от
makeCoffeeбезreturn. - Задача создана и ждёт своей очереди, а
print("order")печатаетorder. try await order.valueждёт конца задачи и отдаёт её результат.makeCoffeeможет выбросить ошибку, иvalueпередаст её дальше, поэтому нуженtry.- В
drinkпопадает строка"coffee", иprint(drink)печатаетcoffee.
Частая ошибка
Задачу запустили, но не дождались:
Task { // ✗
print("inside")
}
print("end")Программа печатает только end, и так при каждом запуске. Строки файла ни разу не останавливаются на await, поэтому задача не получает очереди. После print("end") программа завершается, не дожидаясь задач, и до кода в скобках дело так и не доходит. Кафе закрылось, а листок остался на штыре.
В приложении, которое работает дальше, задача выполнится, а здесь её нужно дождаться через await task.value, как в примере выше.
Главное
Task { … }создаёт задачу — работу, которую Swift выполняет отдельно от остального кода; внутри её скобокawaitразрешён.- Задача в
main.swiftполучает очередь, только когда строки файла остановятся наawait; сама программа задачу не ждёт. await task.valueждёт конца задачи и отдаёт её результат; если код задачи может выбросить ошибку, пишутtry await.