Одновременно: async let
Теперь вы заказываете чай и кофе сразу, у двух бариста, и получаете два пейджера. Напитки готовятся одновременно, и ждать приходится столько, сколько готовится самый долгий из них.
Два await подряд так не умеют: второй вызов начинается, только когда закончился первый. Чтобы две работы шли одновременно, в Swift есть async let:
func brew(_ drink: String, ms: Int) async throws -> String {
try await Task.sleep(for: .milliseconds(ms))
return drink
}
async let tea = brew("tea", ms: 100)
async let coffee = brew("coffee", ms: 50)
let (first, second) = try await (tea, coffee)
print(first, second) // tea coffeeДве работы сразу
async let объявляет константу, значение которой начинают вычислять сразу, а читают позже. Разберём программу по строкам:
async let tea = brew("tea", ms: 100)запускает приготовление чая и сразу идёт дальше, не дожидаясь результата.async let coffee = brew("coffee", ms: 50)запускает кофе. Теперь обе работы идут одновременно, и каждая из них — задача: Swift выполняет её отдельно от остального кода.try await (tea, coffee)ждёт оба напитка и раскладывает их по константамfirstиsecond.tryнужен, потому чтоbrewможет выбросить ошибку.print(first, second)печатаетtea coffee.
Паузы чая и кофе идут одновременно, поэтому программа ждёт примерно столько, сколько готовится чай, самый долгий напиток: около 100 мс. Те же два вызова через await подряд ждали бы сумму пауз — не меньше 150 мс.
Где в этой программе стоят await и try:
- в строке с
async letне нужны ниawait, ниtry: работа там только запускается, а ошибка, если она случится, придёт туда, где значение читают; try awaitнужен там, где значение читают: если работа ещё не закончилась, программа подождёт её на этой строке.
Порядок результатов
Кофе готовится 50 мс, вдвое быстрее чая. И всё же программа печатает tea coffee, а не coffee tea. Порядок результатов задаёт код: в (tea, coffee) чай стоит первым, а то, какая работа закончилась раньше, на порядок не влияет.
С пейджерами так же: какой бы ни прозвенел первым, чашки вы ставите на поднос в том порядке, какой выбрали сами.
Здесь картинка с двумя бариста неточна. Бариста в кафе всегда двое, а компьютер сам решает, сколько работ выполнять в одно и то же мгновение. Swift обещает другое: обе работы начаты сразу, и ни одна не ждёт конца другой.
Этот вопрос часто задают на собеседовании: что в таком коде выполняется одновременно? Ответ:
- работы, запущенные через
async let, идут одновременно; - вызовы через
awaitподряд идут по очереди, сколько бы их ни было; - значение
async letвсё равно читают черезawait, и на этой строке программа ждёт, пока работа закончится.
Частая ошибка
Две работы запустили через await подряд и ждали, что они пойдут одновременно:
let cupOfTea = try await brew("tea", ms: 100) // ✗
let cupOfCoffee = try await brew("coffee", ms: 50)
print(cupOfTea, cupOfCoffee)Программа печатает то же tea coffee, но ждёт не меньше 150 мс вместо примерно 100: кофе начинают готовить, только когда чай уже готов. Компилятор ошибки не покажет, ведь программа работает верно, только медленнее. Исправление — async let, как в примере выше.
Главное
async letзапускает работу сразу и не ждёт её; ждут там, где значение читают черезawait.- Работы, запущенные через
async let, идут одновременно, и общее ожидание примерно равно самой долгой из них. - Порядок результатов задаёт код, а не то, какая работа закончилась первой.
- Два
awaitподряд выполняются по очереди, а не одновременно.