Правильный способ последовательного выполнения асинхронных операций

Мне нужно выполнить асинхронную операцию для каждого элемента в массиве, по одному за раз. Эта операция вызывает обратный вызов в основной очереди.

func fetchResults(for: array, completion: () -> Void) {

    var results: [OtherObject]: []
    let queue = DispatchQueue(label: "Serial Queue")
    queue.sync {

        let group = DispatchGroup()
        for object in array {

            group.enter()
            WebService().fetch(for: object) { result in
                // Calls back on main queue
                // Handle result
                results.append(something)

                group.leave()
            }
            group.wait()
        }
    }

    print(results) // Never reached
    completion()
}

Вызов WebService не перезванивает, что, я думаю, говорит мне, что основная очередь заблокирована, но я не могу понять, почему.


person Ashley Mills    schedule 20.09.2018    source источник
comment
Проверьте это: developer.apple.com/documentation/foundation/nsoperationqueue   -  person sloik    schedule 20.09.2018
comment
Привет, не могли бы вы снова открыть этот вопрос, который вы закрыли? Область действия другая (изменение 1 точки, а не всей точки), и поэтому повторяющаяся цель неприменима: stackoverflow.com/questions/51303719/   -  person Cœur    schedule 08.03.2019


Ответы (3)


Вы должны использовать group.notify(), а не group.wait(), так как последний является синхронной, блокирующей операцией.

Я также не вижу смысла отправлять в очередь, если вы отправляете один рабочий элемент только один раз.

func fetchResults(for: array, completion: () -> Void) {

    var results: [OtherObject]: []
    let group = DispatchGroup()
    for object in array {
        group.enter()
        WebService().fetch(for: object) { result in
            // Calls back on main queue
            // Handle result
            results.append(something)

            group.leave()
        }
    }

    group.notify(queue: DispatchQueue.main) {
        print(results)
        completion()
    }
}
person Dávid Pásztor    schedule 20.09.2018
comment
ему нужен именно сериал поэтому и очередь - person Sh_Khan; 20.09.2018
comment
@Sh_Khan, объявляющий последовательную очередь, не гарантирует последовательное выполнение, если вы выполняете в ней асинхронные задачи. Вот для чего нужен DispatchGroup. - person Dávid Pásztor; 20.09.2018
comment
Я завернул все это в последовательную очередь в тщетной попытке остановить блокировку основной очереди. - person Ashley Mills; 20.09.2018
comment
@AshleyMills делает это внутри цикла for, делает тот же эффект, о котором вы упоминаете, и, что важно, разделяет каждую полученную единицу, делая ее отдельной асинхронной. - person Sh_Khan; 20.09.2018
comment
Это работает. Я пробовал это несколько раз в предыдущих проектах и ​​должен быть отмечен как принятый ответ. - person Jad Ghadry; 20.09.2018
comment
@JadGhadry нет, это не гарантирует последовательный порядок загрузок, должна быть последовательная очередь - person Sh_Khan; 20.09.2018
comment
@Sh_Khan, похоже, вы неправильно понимаете, как работают DispatchQueue и DispatchGroup при выполнении на них асинхронных блоков. Вы не можете гарантировать последовательное выполнение с использованием DispatchQueue.sync{}, даже если очередь является последовательной, если только блок, который вы отправляете с использованием самого sync, не является синхронным. .enter(), .leave() и .notify() в DispatchGroup — это способ сериализации асинхронного выполнения. - person Dávid Pásztor; 20.09.2018
comment
простая демонстрация создайте цикл for внутри него async { // здесь используйте SDWebimage }, и изображения будут загружаться последовательно, несмотря на то, что sdwebimage асинхронный, это может занять некоторое время, но докажет это вам, скажем, для 10 изображений - person Sh_Khan; 20.09.2018
comment
Ну, взорви меня. Получается, что с group.notify вместо group.wait не нужна последовательная очередь. Благодарю вас! - person Ashley Mills; 20.09.2018
comment
@Sh_Khan использует простой встроенный асинхронный метод, такой как URLRequest или простой вызов DispatchQueue.asyncAfter, и вы увидите, что ошибаетесь. Голоса людей, которые действительно тестировали мой код, говорят сами за себя. - person Dávid Pásztor; 20.09.2018
comment
Нет, служба op block() запускается внутри самой очереди, sdweimage достаточно умен, чтобы отправлять только в том случае, если код находится внутри основного блока, я имею в виду, что асинхронный процесс также должен выполняться в той же очереди. - person Sh_Khan; 20.09.2018

Может быть, это просто опечатка, но в принципе не запускайте очередь syncхронически.

Затем вместо wait используйте notify вне(!) цикла и печатайте results в очереди.

queue.async {

    let group = DispatchGroup()
    for object in array {

        group.enter()
        WebService().fetch(for: object) { result in
            // Calls back on main queue

            // Handle result
            results.append(something)

            group.leave()
        }
    }
    group.notify(queue: DispatchQueue.main) {
        print(results)
        completion()
    }
}
person vadian    schedule 20.09.2018
comment
Вопрос, который я хотел задать, действительно был по одному последовательно, но это ответ на вопрос, который я задавал, и оказывается, что мне не нужно делать это последовательно в любом случае. Спасибо. - person Ashley Mills; 20.09.2018

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

Вот что сработало для меня, может быть, это поможет:

class func synchronize(completion: @escaping (_ error: Bool) -> Void) {

    DispatchQueue.global(qos: .background).async {

        // Background Thread
        var error = false
        let group = DispatchGroup()
        synchronizeObject1(group: group){ error = true }
        synchronizeObject2(group: group){ error = true }
        synchronizeObject3(group: group){ error = true }
        group.wait() // will wait for everyone to sync

        DispatchQueue.main.async {
            // Run UI Updates or call completion block
            completion(error)
        }
    }
}




class func synchronizeObject1(group: DispatchGroup, errorHandler: @escaping () -> Void){

    group.enter()
    WebservicesController.shared.getAllObjects1() { _ in

        // Do My stuff

        // Note: if an error occures I call errorHandler()

        group.leave()
    }
}

Если бы я сказал, это может исходить от queue.sync вместо queue.async. Но я не эксперт по асинхронным вызовам.

Надеюсь, поможет

person Olympiloutre    schedule 20.09.2018