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

Как с помощью DispatchGroup завершить выполнение последовательных запросов, если на третьем нужно выйти, не дожидаясь четвертого?

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

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

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

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

Использование DispatchGroup для управления последовательными запросами

Для решения задачи прерывания последовательных запросов при выполнении определенного условия (в вашем случае — после третьего запроса) с использованием DispatchGroup необходимо комбинировать несколько подходов, так как сам DispatchGroup предназначен в первую очередь для ожидания завершения группы задач, а не для управления их потоком с прерыванием.

Основной подход с флагом прерывания

Ключевая идея — использовать флаг (cancel flag) для отслеживания необходимости прерывания цепочки запросов. Вот реализация:

import Foundation

class NetworkService {
    private let dispatchGroup = DispatchGroup()
    private var shouldCancel = false
    private let cancelLock = NSLock()
    
    func executeSequentialRequests(completion: @escaping (Result<[String], Error>) -> Void) {
        var results: [String] = []
        let queue = DispatchQueue(label: "com.example.network.queue", attributes: .concurrent)
        
        // Запрос 1
        dispatchGroup.enter()
        performRequest(id: 1) { [weak self] result in
            queue.async {
                guard let self = self else { return }
                
                self.cancelLock.lock()
                let shouldContinue = !self.shouldCancel
                self.cancelLock.unlock()
                
                if shouldContinue {
                    switch result {
                    case .success(let data):
                        results.append(data)
                    case .failure(let error):
                        self.cancelAllRequests()
                    }
                }
                self.dispatchGroup.leave()
            }
        }
        
        // Запрос 2
        dispatchGroup.enter()
        performRequest(id: 2) { [weak self] result in
            queue.async {
                guard let self = self else { return }
                
                self.cancelLock.lock()
                let shouldContinue = !self.shouldCancel
                self.cancelLock.unlock()
                
                if shouldContinue {
                    switch result {
                    case .success(let data):
                        results.append(data)
                    case .failure(let error):
                        self.cancelAllRequests()
                    }
                }
                self.dispatchGroup.leave()
            }
        }
        
        // Запрос 3 - критический, после которого может потребоваться отмена
        dispatchGroup.enter()
        performRequest(id: 3) { [weak self] result in
            queue.async {
                guard let self = self else { return }
                
                self.cancelLock.lock()
                let shouldContinue = !self.shouldCancel
                self.cancelLock.unlock()
                
                if shouldContinue {
                    switch result {
                    case .success(let data):
                        results.append(data)
                        // Проверяем условие для прерывания
                        if data.contains("cancel_condition") {
                            self.cancelAllRequests()
                        }
                    case .failure(let error):
                        self.cancelAllRequests()
                    }
                }
                self.dispatchGroup.leave()
            }
        }
        
        // Запрос 4 - выполнится только если не было отмены
        dispatchGroup.enter()
        performRequest(id: 4) { [weak self] result in
            queue.async {
                guard let self = self else { return }
                
                self.cancelLock.lock()
                let shouldContinue = !self.shouldCancel
                self.cancelLock.unlock()
                
                if shouldContinue {
                    switch result {
                    case .success(let data):
                        results.append(data)
                    case .failure(let error):
                        self.cancelAllRequests()
                    }
                }
                self.dispatchGroup.leave()
            }
        }
        
        // Ожидаем завершения всех НЕОТМЕНЕННЫХ запросов
        dispatchGroup.notify(queue: .main) {
            completion(.success(results))
        }
    }
    
    private func cancelAllRequests() {
        cancelLock.lock()
        shouldCancel = true
        cancelLock.unlock()
    }
    
    private func performRequest(id: Int, completion: @escaping (Result<String, Error>) -> Void) {
        // Имитация сетевого запроса
        DispatchQueue.global().asyncAfter(deadline: .now() + Double(id) * 0.5) {
            if id == 3 {
                // Симулируем условие для отмены
                completion(.success("Data \(id) with cancel_condition"))
            } else {
                completion(.success("Data \(id)"))
            }
        }
    }
}

Альтернативный подход с OperationQueue

Более элегантное решение — использование OperationQueue с зависимостями:

class OperationBasedNetworkService {
    func executeRequestsWithCancellation(completion: @escaping ([String]) -> Void) {
        let queue = OperationQueue()
        queue.maxConcurrentOperationCount = 1 // Последовательное выполнение
        
        var results: [String] = []
        let resultLock = NSLock()
        
        let request1 = BlockOperation {
            let result = self.simulateRequest(id: 1)
            resultLock.lock()
            results.append(result)
            resultLock.unlock()
        }
        
        let request2 = BlockOperation {
            let result = self.simulateRequest(id: 2)
            resultLock.lock()
            results.append(result)
            resultLock.unlock()
        }
        
        let request3 = BlockOperation {
            let result = self.simulateRequest(id: 3)
            resultLock.lock()
            results.append(result)
            resultLock.unlock()
            
            // Условие для прерывания
            if result.contains("cancel_condition") {
                queue.cancelAllOperations() // Отменяем все последующие операции
            }
        }
        
        let request4 = BlockOperation {
            let result = self.simulateRequest(id: 4)
            resultLock.lock()
            results.append(result)
            resultLock.unlock()
        }
        
        // Устанавливаем зависимости
        request2.addDependency(request1)
        request3.addDependency(request2)
        request4.addDependency(request3)
        
        // Добавляем completion operation
        let completionOperation = BlockOperation {
            DispatchQueue.main.async {
                completion(results)
            }
        }
        
        completionOperation.addDependency(request4)
        
        // Добавляем все операции в очередь
        queue.addOperations([request1, request2, request3, request4, completionOperation], 
                          waitUntilFinished: false)
    }
    
    private func simulateRequest(id: Int) -> String {
        // Имитация запроса
        return id == 3 ? "Data \(id) with cancel_condition" : "Data \(id)"
    }
}

Ключевые моменты решения

  1. Thread-safe управление флагом отмены: Используем NSLock или DispatchSemaphore для безопасного доступа к флагу отмены из разных потоков.

  2. Проверка флага в каждом запросе: Перед выполнением логики каждого запроса проверяем, не установлен ли флаг отмены.

  3. Условный выход на третьем запросе: В обработчике завершения третьего запроса анализируем результат и устанавливаем флаг отмены при необходимости.

  4. DispatchGroup для синхронизации: Несмотря на возможность отмены, DispatchGroup отслеживает завершение всех запросов, которые успели начать выполнение.

  5. Атомарность операций: Все операции с разделяемыми данными (флаг отмены, массив результатов) выполняются в критической секции.

Когда использовать этот подход

  • Когда запросы уже запущены и нужно предотвратить обработку результатов последующих запросов
  • В сценариях, где важна последовательная обработка, но возможны условные прерывания
  • Когда нужно гарантировать, что все начатые запросы корректно завершат свою работу

Ограничения подхода

  1. Не отменяет выполняющиеся запросы: Фактические сетевые запросы продолжают выполняться, мы просто игнорируем их результаты.
  2. Накладные расходы: Проверка флага в каждом обработчике добавляет небольшие накладные расходы.

Для полной отмены сетевых запросов лучше использовать URLSessionTask с методом cancel() или Combine framework с операторами управления жизненным циклом.

Как с помощью DispatchGroup завершить выполнение последовательных запросов, если на третьем нужно выйти, не дожидаясь четвертого? | PrepBro