← Назад к вопросам

Как узнать, когда асинхронные задачи на concurrent очереди закончат выполнение?

2.0 Middle🔥 162 комментариев
#Многопоточность и асинхронность

Комментарии (2)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Методы отслеживания завершения асинхронных задач на 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) задач завершены (через семафор)")

Рекомендации по выбору метода

  1. Для новых проектов используйте Swift Concurrency (async/await, TaskGroup) — это современный, безопасный и эффективный подход.

  2. Для поддержки старых версий iOS (< iOS 13) используйте DispatchGroup или OperationQueue.

  3. Для сложных зависимостей между задачами лучше всего подходит OperationQueue.

  4. Для контроля за параллельным доступом к ресурсам используйте семафоры.

  5. Избегайте блокирующих вызовов (wait()) на главном потоке — это приводит к фризам UI.

Каждый метод имеет свои особенности, и выбор зависит от конкретных требований проекта, версии iOS и сложности синхронизации задач. Современная тенденция — переход от низкоуровневых GCD конструкций к Swift Concurrency, который предоставляет более безопасные и выразительные абстракции для работы с асинхронным кодом.