Ожидание без блокировки
Чему вы научитесь:
- объяснять, чем ожидание через
awaitотличается от ожидания, которое останавливает программу; - объявлять асинхронные функции и вызывать их через
awaitиtry await; - запускать две работы одновременно через
async let, а отдельную задачу — черезTask; - защищать общие данные актором и объяснять, что такое гонка данных.

Представьте кафе без пейджеров. Вы заказываете латте и стоите у кассы, пока бариста его готовит. Очередь за вами стоит тоже: кассир не примет следующий заказ, пока вы не получите свой.
Теперь то же кафе с пейджерами. Над кассой висит вывеска: заказы выдают по сигналу. Вы делаете заказ, получаете пейджер и отходите к столику, а кассир тут же обслуживает следующего покупателя. Пейджер звенит, вы забираете чашку и продолжаете с того места, где остановились.
Программам тоже приходится ждать: ответа от сервера, чтения файла, сигнала таймера. Ждать можно двумя способами:
- с блокировкой, как у кассы без пейджера: пока ответа нет, эта часть программы стоит на месте и больше ничего не делает;
- без блокировки, как с пейджером: пока ответа нет, программа может делать другую работу.
Зачем это нужно? Если приложение на телефоне с блокировкой ждёт ответа сервера, оно замирает: экран не листается, кнопки не нажимаются. Без блокировки приложение рисует экран и отвечает на касания, пока ответ ещё в пути.
В Swift вывеска над кассой — слово async в объявлении функции, а пейджер в руке — слово await при её вызове. Посмотрим на них в коде:
func makeCoffee() async throws -> String {
try await Task.sleep(for: .milliseconds(100))
return "coffee"
}
print("order") // order
let drink = try await makeCoffee()
print(drink) // coffeeАсинхронная функция
Асинхронная функция — функция, которая может приостановиться посреди работы, пока чего-то ждёт, а потом продолжить. Её отмечают словом async в заголовке, после скобок с параметрами.
Давайте разберём программу по шагам:
func makeCoffee() async throws -> Stringобъявляет асинхронную функцию. Словоasyncстоит после параметров, передthrowsи стрелкой.- Внутри неё
Task.sleep(for: .milliseconds(100))ждёт 100 миллисекунд, не блокируя программу. В настоящем приложении здесь был бы запрос к серверу, а пауза его изображает. print("order")печатаетorder.try await makeCoffee()вызывает функцию и ждёт её результата. Когда пауза кончится, вdrinkпопадёт строка"coffee".print(drink)печатаетcoffee.
await и точка приостановки
Слово await ставят перед каждым вызовом асинхронной функции. Оно отмечает точку приостановки — место, где код может остановиться и подождать. Пока код ждёт, Swift отдаёт освободившееся время другой работе, как кассир, который обслуживает следующего покупателя.
Когда результат готов, код продолжает работу с того же места. Все переменные функции на месте, поэтому продолжение выглядит так, будто паузы не было.
Писать await можно там, где приостановка разрешена:
- внутри асинхронной функции;
- прямо в
main.swiftвне функций, какtry await makeCoffee()в примере.
В обычной функции, без async, await не скомпилируется: приостанавливаться ей не разрешено.
Здесь картинка с пейджером неточна. Покупатель у столика свободен: может позвонить или почитать. Функция на await ничего не делает, она только ждёт, а другую работу выполняет остальная программа.
Пауза и try
Task.sleep объявлена с throws: ожидание можно отменить, и тогда она выбросит ошибку. Поэтому:
Task.sleepвызывают черезtry await;- функция
makeCoffee, которая вызывает её сtry, сама объявлена сthrows; - порядок слов постоянный: в объявлении
async throws, в вызовеtry await.
Как вы уже знаете, в главной части программы, то есть в main.swift вне функций, try работает и без do/catch. Если ошибку никто не поймает, программа аварийно завершится.
Частая ошибка
Асинхронную функцию вызвали без await:
let second = try makeCoffee() // ✗error: expression is 'async' but is not marked with 'await'Программа не компилируется. Вызов асинхронной функции может приостановить код, и Swift требует, чтобы такое место было видно в тексте программы. Исправление — try await makeCoffee().
Главное
- Ожидание без блокировки оставляет программе время на другую работу, пока ответ в пути.
asyncв объявлении делает функцию асинхронной, аawaitпри вызове отмечает точку приостановки.awaitпишут внутри асинхронной функции и вmain.swiftвне функций; если функция ещё и выбрасывает ошибки, порядок такой:async throwsв объявлении,try awaitв вызове.