Различия между DispatchQueue.async и DispatchQueue.sync

Оба метода – async и sync – используются для выполнения кода асинхронно в DispatchQueue, но делают это совершенно по-разному, что влияет на поведение вашего приложения. Понимание этих различий критически важно для написания отзывчивых и корректных многопоточных приложений на Swift (и Objective-C с использованием Grand Central Dispatch).

DispatchQueue.async

DispatchQueue.async планирует блок кода для выполнения асинхронно в указанной очереди. Ключевые характеристики:

  • Неблокирующий вызов: Функция async немедленно возвращает управление вызывающему потоку, даже если задача еще не завершена.
  • Выполнение в фоновом режиме: Блок кода будет выполнен в один из свободных потоков очереди, когда это возможно. Если очередь пуста, задача будет выполнена текущим потоком (если он доступен).
  • Параллелизм: Если очередь настроена для параллельного выполнения, несколько задач могут выполняться одновременно.
  • Порядок не гарантируется (по умолчанию): Порядок выполнения задач в очереди не гарантируется, если только очередь не является сериальной (последовательной).

Пример:

DispatchQueue.global().async {
    // Длительная операция, например, сетевой запрос или обработка данных

    print("Начало выполнения задачи в фоне")

    Thread.sleep(forTimeInterval: 2) // Имитация длительной операции

    print("Завершение выполнения задачи в фоне")

}
print("Продолжение выполнения основного потока")

В этом примере, async планирует блок кода для выполнения в глобальной очереди. Основной поток не блокируется и сразу же выводит “Продолжение выполнения основного потока”. Блок кода внутри async будет выполнен позже, когда очередь освободится.

DispatchQueue.sync

DispatchQueue.sync планирует блок кода для синхронного выполнения в указанной очереди. Ключевые характеристики:

  • Блокирующий вызов: Функция sync блокирует вызывающий поток до тех пор, пока задача не будет полностью завершена.
  • Выполнение в текущем потоке (если возможно): Если очередь настроена для последовательного выполнения и текущий поток является потоком очереди, задача выполняется непосредственно в этом потоке. В противном случае, задача будет выполнена в одном из свободных потоков очереди.
  • Гарантированный порядок: Задачи выполняются в том порядке, в котором они были добавлены в очередь (для сериальных очередей).

Пример:

DispatchQueue.global().sync {
    // Длительная операция

    print("Начало выполнения задачи синхронно")

    Thread.sleep(forTimeInterval: 2) // Имитация длительной операции

    print("Завершение выполнения задачи синхронно")
}

print("Продолжение выполнения основного потока (после завершения задачи)")

В этом примере, sync планирует блок кода для синхронного выполнения в глобальной очереди. Основной поток блокируется до тех пор, пока задача не будет полностью завершена и не выведет “Завершение выполнения задачи синхронно”. Только после этого основной поток продолжит выполнение и выведет “Продолжение выполнения основного потока (после завершения задачи)”.

Сравнение в таблице

ХарактеристикаDispatchQueue.asyncDispatchQueue.sync
Блокировка потокаНе блокируетБлокирует вызывающий поток
ВыполнениеАсинхронно, в фоновом режимеСинхронно, в текущем потоке (если возможно)
Порядок выполненияНе гарантируется (по умолчанию)Гарантируется (для сериальных очередей)
ИспользованиеДля выполнения длительных операций без блокировки UIДля выполнения коротких операций, требующих синхронизации или гарантии порядка

Важные замечания и потенциальные проблемы

  • Deadlock: Использование sync внутри блока кода, который уже выполняется в той же очереди, может привести к deadlock. Например, если вы вызываете sync из блока кода, запланированного с помощью async на ту же очередь, текущий поток будет ждать завершения задачи, а задача не сможет завершиться, пока поток не освободится (что невозможно, так как он уже ждет).
  • UI-блокировка: Никогда не выполняйте длительные операции синхронно в основном потоке (DispatchQueue.main). Это приведет к зависанию UI и плохому пользовательскому опыту. Используйте async для выполнения таких операций в фоновом режиме, а затем обновите UI из основного потока после завершения задачи.
  • Сериальные очереди: Для сериальных очередей sync гарантирует порядок выполнения задач. Это полезно, когда вам нужно выполнить несколько операций последовательно и избежать гонок данных.

Когда что использовать?

  • Используйте DispatchQueue.async для большинства случаев, особенно для длительных операций, которые не должны блокировать основной поток UI.
  • Используйте DispatchQueue.sync только тогда, когда вам абсолютно необходимо синхронно выполнить короткую операцию и гарантировать ее завершение перед продолжением выполнения текущего потока. Будьте осторожны с потенциальными deadlock.

В заключение, выбор между async и sync зависит от конкретных требований вашей задачи. Понимание их различий поможет вам писать более эффективный, отзывчивый и надежный код.


Будет интересно

Nikulux в Telegram

Статьи, архивы материалов, скоро ещё и мемы

(= Подписаться =)