Как узнать, когда асинхронные задачи на concurrent очереди закончат выполнение?
Комментарии (2)
Ответ сгенерирован нейросетью и может содержать ошибки
Методы отслеживания завершения асинхронных задач на concurrent очереди
Отслеживание завершения асинхронных задач на concurrent очереди (параллельной очереди) — важнейший аспект многопоточного программирования в iOS. Существует несколько подходов, которые можно разделить на низкоуровневые (GCD) и высокоуровневые (Async/Await).
1. DispatchGroup — классический подход в GCD
DispatchGroup позволяет группировать задачи и получать уведомление о завершении всех из них. Это наиболее универсальный метод для GCD.
import Foundation
let concurrentQueue = DispatchQueue(label: "com.example.concurrent", attributes: .concurrent)
let dispatchGroup = DispatchGroup()
// Добавляем несколько асинхронных задач
for i in 1...5 {
dispatchGroup.enter() // Входим в группу
concurrentQueue.async {
print("Задача \(i) началась")
sleep(UInt32.random(in: 1...3))
print("Задача \(i) завершилась")
dispatchGroup.leave() // Покидаем группу
}
}
// Основные варианты обработки завершения:
// Вариант 1: notify (неблокирующий)
dispatchGroup.notify(queue: .main) {
print("✅ Все задачи в concurrent очереди завершены")
}
// Вариант 2: wait (блокирующий, использовать осторожно)
// dispatchGroup.wait() // Блокирует текущий поток
// dispatchGroup.wait(timeout: .now() + 5) // С таймаутом
Ключевые особенности DispatchGroup:
enter()иleave()должны быть сбалансированыnotify()не блокирует вызывающий потокwait()блокирует текущий поток (опасно для main thread)- Подходит для любых типов очередей (serial и concurrent)
2. DispatchWorkItem с барьером (barrier)
Для concurrent очередей можно использовать барьерные задачи, которые выполняются только после всех предыдущих.
let concurrentQueue = DispatchQueue(label: "com.example.barrier", attributes: .concurrent)
// Добавляем обычные задачи
for i in 1...3 {
concurrentQueue.async {
print("Параллельная задача \(i)")
}
}
// Барьерная задача ждет завершения всех предыдущих
concurrentQueue.async(flags: .barrier) {
print("⛔️ Барьер: все предыдущие задачи завершены")
}
// Последующие задачи выполняются после барьера
concurrentQueue.async {
print("Задача после барьера")
}
3. OperationQueue с зависимостями
OperationQueue предоставляет более высокоуровневый API с богатыми возможностями управления задачами.
import Foundation
let operationQueue = OperationQueue()
operationQueue.maxConcurrentOperationCount = 4 // Контролируем параллелизм
// Создаем операции
let operations = (1...5).map { i in
BlockOperation {
print("Операция \(i) выполняется")
sleep(1)
}
}
let completionOperation = BlockOperation {
print("🎉 Все операции завершены")
}
// Устанавливаем зависимости
operations.forEach { operation in
completionOperation.addDependency(operation)
}
// Добавляем все операции в очередь
operationQueue.addOperations(operations + [completionOperation], waitUntilFinished: false)
Преимущества OperationQueue:
- Управление приоритетами (
qualityOfService) - Возможность отмены операций (
cancel()) - Сложные цепочки зависимостей
- KVO-наблюдение за состоянием (
isFinished,isExecuting)
4. Современный подход: async/await с TaskGroup
С появлением Swift Concurrency в Swift 5.5+ рекомендуется использовать современные конструкции.
import Foundation
func performConcurrentTasks() async {
await withTaskGroup(of: Void.self) { group in
// Добавляем задачи в группу
for i in 1...5 {
group.addTask {
print("Async задача \(i) началась")
try? await Task.sleep(nanoseconds: UInt64.random(in: 1_000_000_000...3_000_000_000))
print("Async задача \(i) завершилась")
}
}
// Ожидаем завершения всех задач
await group.waitForAll()
print("✅ Все async задачи завершены")
}
}
// Использование
Task {
await performConcurrentTasks()
}
Преимущества Swift Concurrency:
- Структурный параллелизм (structured concurrency)
- Автоматическое управление памятью
- Интеграция с async/await
- Безопасность работы с потоками
5. Семафоры (Semaphore) для сложных сценариев
DispatchSemaphore позволяет контролировать доступ к ресурсам и синхронизировать задачи.
let concurrentQueue = DispatchQueue(label: "com.example.semaphore", attributes: .concurrent)
let semaphore = DispatchSemaphore(value: 0) // Начинаем с 0
var completedTasks = 0
let totalTasks = 5
for i in 1...totalTasks {
concurrentQueue.async {
print("Задача \(i) выполняется")
sleep(1)
// Увеличиваем счетчик завершенных задач
objc_sync_enter(self)
completedTasks += 1
objc_sync_exit(self)
// Если все задачи завершены, сигнализируем семафору
if completedTasks == totalTasks {
semaphore.signal()
}
}
}
// Ждем сигнала от семафора
semaphore.wait()
print("Все \(totalTasks) задач завершены (через семафор)")
Рекомендации по выбору метода
-
Для новых проектов используйте Swift Concurrency (async/await, TaskGroup) — это современный, безопасный и эффективный подход.
-
Для поддержки старых версий iOS (< iOS 13) используйте DispatchGroup или OperationQueue.
-
Для сложных зависимостей между задачами лучше всего подходит OperationQueue.
-
Для контроля за параллельным доступом к ресурсам используйте семафоры.
-
Избегайте блокирующих вызовов (
wait()) на главном потоке — это приводит к фризам UI.
Каждый метод имеет свои особенности, и выбор зависит от конкретных требований проекта, версии iOS и сложности синхронизации задач. Современная тенденция — переход от низкоуровневых GCD конструкций к Swift Concurrency, который предоставляет более безопасные и выразительные абстракции для работы с асинхронным кодом.