Семафоры: несколько операций


#1

Не могу до конца разобраться с семафорами. Есть функция обновления данных (пример ниже)

func update(completionHandler: (() -> ())? = nil) {
    
    let semaphore = DispatchSemaphore(value: blocks.count)
    
    DispatchQueue.global(qos: .userInitiated).async {
        
        blocks.forEach { block in
            
//            semaphore.wait()
            
            getData(block) {
                semaphore.signal()
            }
        }
        
        semaphore.wait()
        completionHandler?()
    }
}

Есть несколько элементов. Необходимо обновить данные у каждого и по завершении выполнить completionHandler. Но почему-то он выполняется, когда данные не еще обновились :face_with_raised_eyebrow:


#2

Ждите async/await, будет в следующей версии свифта :slight_smile:


#3

semaphore.wait() срабатывает после срабатывания semaphore.signal(). У вас сигнал на первой итерации forEach срабатывает - вот и ответ. Вам надо семафор настроить внутри блока getData, чтобы контролировать каждое срабатывание getData. Попробуйте так:

   blocks.forEach { block in
        let semaphore = DispatchSemaphore(value: blocks.count)
        getData(block) {
            ...
            semaphore.signal()
        }
        semaphore.wait()
    }

#4

Очень удобно будет! :+1: Только когда она выйдет? :grinning:


#5

У меня заработало в таком варианте:

func update(completionHandler: (() -> ())? = nil) {
    
    let semaphore = DispatchSemaphore(value: 0) //0!?
         
        blocks.forEach { block in
            
            getData(block) {
                ...
                semaphore.signal()
            }
        semaphore.wait()
        }
        
        completionHandler?()
}

Получается, что использование отдельного потока (DispatchQueue.global(qos: .userInitiated).async {}) необязательно?


#6

Ожидание семафора semaphore.wait() должно быть всегда запущено не в основном потоке :slight_smile: иначе зафризити интрефейс.
Поскольку у вас семафор в клоужере, он уже в отдельном потоке, но можно подстраховаться - лишним не будет.

ПС
для.wait(timeout: DispatchTime) можно указать таймаут для отрабатывания разрыва соединения, иначе тоже можно повиснуть навсегда :slight_smile:


#7

У вас теперь последовательная загрузка данных. Для первоначальной идеи, если не надо ограничивать количество загрузок, можно использовать DispatchGroup:

func update(completionHandler: (() -> ())? = nil) {
    let group = DispatchGroup()

    blocks.forEach { block in
        group.enter()
        getData(block) {
            ...
            group.leave()
        }
    }

    group.notify(queue: .main) { // может не main
        completionHandler?()
    }
}

#8

А есть принципиальная разница между использованием DispatchGroup и DispatchSemaphore?


#9

В вашем случае при решении для семафора блок ждёт завершения работы предыдущего и потом начинает сам отрабатывать. DispatchGroup позволяет запускать работу блоков параллельно.


#10

В swift 5.4 “она” не вошла, он выйдет 7 января. Предполагаю появится в swift 5.5, ближе к маю или в июне на WWDC.

P.S. Может это уже будет swift 6.
P.P.S. Попробовать можно уже сейчас

Implementation: Available in recent main snapshots behind the flag -Xfrontend -enable-experimental-concurrency