Перейти к содержимому
Теория · Асинхронный код · урок 1 из 7

Ожидание без блокировки

Чему вы научитесь

Чему вы научитесь:

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

Представьте кафе без пейджеров. Вы заказываете латте и стоите у кассы, пока бариста его готовит. Очередь за вами стоит тоже: кассир не примет следующий заказ, пока вы не получите свой.

Теперь то же кафе с пейджерами. Над кассой висит вывеска: заказы выдают по сигналу. Вы делаете заказ, получаете пейджер и отходите к столику, а кассир тут же обслуживает следующего покупателя. Пейджер звенит, вы забираете чашку и продолжаете с того места, где остановились.

Программам тоже приходится ждать: ответа от сервера, чтения файла, сигнала таймера. Ждать можно двумя способами:

  • с блокировкой, как у кассы без пейджера: пока ответа нет, эта часть программы стоит на месте и больше ничего не делает;
  • без блокировки, как с пейджером: пока ответа нет, программа может делать другую работу.

Зачем это нужно? Если приложение на телефоне с блокировкой ждёт ответа сервера, оно замирает: экран не листается, кнопки не нажимаются. Без блокировки приложение рисует экран и отвечает на касания, пока ответ ещё в пути.

В Swift вывеска над кассой — слово async в объявлении функции, а пейджер в руке — слово await при её вызове. Посмотрим на них в коде:

SWIFT
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 в заголовке, после скобок с параметрами.

Давайте разберём программу по шагам:

  1. func makeCoffee() async throws -> String объявляет асинхронную функцию. Слово async стоит после параметров, перед throws и стрелкой.
  2. Внутри неё Task.sleep(for: .milliseconds(100)) ждёт 100 миллисекунд, не блокируя программу. В настоящем приложении здесь был бы запрос к серверу, а пауза его изображает.
  3. print("order") печатает order.
  4. try await makeCoffee() вызывает функцию и ждёт её результата. Когда пауза кончится, в drink попадёт строка "coffee".
  5. 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:

SWIFT
let second = try makeCoffee() // ✗
TEXT
error: expression is 'async' but is not marked with 'await'

Программа не компилируется. Вызов асинхронной функции может приостановить код, и Swift требует, чтобы такое место было видно в тексте программы. Исправление — try await makeCoffee().

Главное

  • Ожидание без блокировки оставляет программе время на другую работу, пока ответ в пути.
  • async в объявлении делает функцию асинхронной, а await при вызове отмечает точку приостановки.
  • await пишут внутри асинхронной функции и в main.swift вне функций; если функция ещё и выбрасывает ошибки, порядок такой: async throws в объявлении, try await в вызове.