Что происходит с переключением потоков при await в Swift concurrency?
Что проверяют
Понимаете ли вы, что await это точка приостановки, а не ожидание внутри потока, и знаете ли, что после возобновления поток может оказаться другим. Это граница между «переписал колбэки» и «понимаю модель».
Как отвечать
На await функция отдаёт поток обратно в кооперативный пул, и поток немедленно берёт другую работу. Блокировки нет, поэтому ждущие задачи не съедают потоки.
После возобновления вы можете оказаться на другом потоке пула. Гарантию даёт только изоляция: код актора возобновляется на исполнителе этого актора, помеченный @MainActor возобновляется на главном потоке. У неизолированной асинхронной функции таких обещаний нет, и смотреть в ней на текущий поток бессмысленно.
@MainActor func reload() async {
let data = await service.load()
apply(data) // снова на главном потоке, это даёт изоляция
}Что добавить дальше:
- размер пула привязан к числу ядер и рассчитан на то, что задачи не блокируют потоки;
Task { }наследует изоляцию и приоритет места создания,Task.detachedне наследует ничего;- актор реентерабелен: пока задача стоит на
await, он обслуживает другие вызовы, и состояние после ожидания может отличаться от состояния до него.
Где спотыкаются
Читают await как «подождать здесь же». Обновление интерфейса сразу после ожидания из неизолированного кода то работает, то падает, потому что поток каждый раз разный. Лечится это не проверками потока, а пометкой изоляции.
Тащат в асинхронный код блокирующие примитивы: семафор, чтобы дождаться результата, или сон потока. Пул не заводит новые потоки взамен заблокированных, и приложение встаёт целиком. Ждать нужно через await и Task.sleep.
Считают, что между чтением поля до ожидания и записью после ничего не произошло. За это время актор мог принять другие вызовы и изменить состояние, поэтому предусловия проверяют заново после возобновления, а не только перед ним.