Почему после синхронной операции выполнение кода может продолжаться в другом потоке
Что проверяют
Понимаете ли вы разницу между очередью и потоком и почему код после точки ожидания может продолжиться не там, где начался. Вопрос отсеивает тех, кто выучил DispatchQueue.global().async как заклинание, но не знает, чем именно это оборачивается.
Как отвечать
Главная мысль: очередь это не поток. GCD держит пул потоков и раздаёт им блоки из очередей. Гарантия последовательной очереди в том, что блоки не выполняются одновременно, а вовсе не в том, что их исполняет один и тот же поток.
asyncотдаёт блок очереди и сразу возвращает управление, блок подхватит свободный поток пула;syncблокирует вызывающий поток до конца блока, и сам блок чаще всего выполняется на этом же вызывающем потоке, никакого нового потока не появляется;DispatchQueue.main.asyncвозвращает работу в главный поток, и только оттуда можно трогать интерфейс.
В Swift Concurrency то же самое, но честнее названо: await это точка приостановки. После неё функция возобновляется на исполнителе, которого выбрала среда, и совпадение с прежним потоком не гарантировано. Привязку даёт актор: @MainActor обещает главный поток, обычный актор обещает лишь отсутствие гонок, но не конкретный поток.
Где спотыкаются
Путают sync с «выполнить в фоне». Он ничего не ускоряет, а лишь переносит исполнение под защиту очереди, и вызывающий поток всё это время стоит. Вызов DispatchQueue.main.sync из главного потока это мгновенный дедлок.
Второе: блокирующая работа на глобальной очереди. Сеть с семафором, ожидание замка, сон внутри блока: GCD видит, что потоки заняты, и создаёт новые, пул разрастается, приложение начинает тормозить на переключениях.
Третье: трогают интерфейс после await, решив, что раз метод начался на главном потоке, там же и продолжится. Нужен @MainActor на типе или методе. Такой же ошибкой будет считать, что актор гарантирует поток: он гарантирует только исключительный доступ к своему состоянию.