Как вызвать синхронную задачу на главном потоке, чтобы избежать дедлока
Что проверяют
Понимаете ли вы, почему DispatchQueue.main.sync с главного потока приводит к взаимной блокировке, и умеете ли обходиться без синхронного ожидания вообще. Следом часто спрашивают про менее очевидные дедлоки и про то, как та же задача решается в Swift Concurrency.
Как отвечать
Сначала механика. sync ставит блок в очередь и блокирует текущий поток до его выполнения. Главная очередь последовательная и обслуживается главным потоком. Если вы уже на главном потоке, он ждёт блок, который сам же должен выполнить: это дедлок. Современный libdispatch часто распознаёт такой вызов и роняет приложение, а не просто вешает его.
Если синхронность действительно нужна:
- проверка потока: если
Thread.isMainThread, выполнить работу сразу, иначе черезDispatchQueue.main.sync. Защищает от вызова с главного потока; - утверждение вместо проверки:
dispatchPrecondition(condition: .onQueue(.main))в местах, которые обязаны идти на главном, чтобы ошибка ловилась ещё в отладке; - async по умолчанию: чаще всего ожидание не нужно, хватает
DispatchQueue.main.asyncи колбэка; - Swift Concurrency: пометить код
@MainActorи вызывать черезawait MainActor.run { }. Поток не блокируется, а задача приостанавливается, поэтому такой дедлок в этой модели получить гораздо сложнее. Если вы точно знаете, что уже на главном акторе, естьMainActor.assumeIsolated.
Затем скажите, что проверка isMainThread закрывает только самый очевидный случай. Дедлок возможен и косвенно: главный поток держит блокировку или делает sync на вашу фоновую последовательную очередь, а та в это время делает main.sync.
Где спотыкаются
Думают, что проверка потока решает проблему полностью, и не видят циклического ожидания через NSLock, семафор или вложенные sync на две очереди.
Используют main.sync там, где хватает async. Фоновый поток простаивает, пока главный занят, а если главный в это время ждёт этот фоновый поток, дедлок получается на ровном месте.
Путают поток и очередь. Проблема не в «главном потоке вообще», а в ожидании своей же последовательной очереди: тот же дедлок даёт sync на любую serial-очередь изнутри неё самой.