3

I need to perform an async operation for each element in an array, one at at time. This operation calls back on the main queue.

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()
}

The WebService call isn't calling back - which I think is telling me the main queue is blocked, but I can't understand why.

Ashley Mills
  • 50,474
  • 16
  • 129
  • 160
  • Check this out: https://developer.apple.com/documentation/foundation/nsoperationqueue – sloik Sep 20 '18 at 17:39
  • Hi, could you reopen this question that you have closed? The scope is different (changing 1 dot, not all dot) and as such the duplicate target is not applicable: https://stackoverflow.com/questions/51303719/uipagecontrol-dot-size-for-current-page – Cœur Mar 08 '19 at 06:37

3 Answers3

4

You should use group.notify() rather than group.wait(), since the latter is a synchronous, blocking operation.

I also don't see a point of dispatching to a queue if you only dispatch a single work item once.

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()
    }
}
Dávid Pásztor
  • 51,403
  • 9
  • 85
  • 116
  • 1
    he needs it a serial that's why the queue – Shehata Gamal Sep 20 '18 at 10:03
  • @Sh_Khan declaring a serial queue won't ensure serial execution if you are executing async tasks on it. That's what the `DispatchGroup` is for. – Dávid Pásztor Sep 20 '18 at 10:04
  • I wrapped the whole thing in a serial queue in a vain attempt to try to stop it blocking the main queue – Ashley Mills Sep 20 '18 at 10:05
  • @AshleyMills making it inside the for loop does the same effect you mention and importantly separate each unit fetched by making it in a separate async – Shehata Gamal Sep 20 '18 at 10:12
  • 1
    This works. I have tried it on several occasions in previous projects, and should be marked as the accepted answer. – Jad Ghadry Sep 20 '18 at 10:21
  • @JadGhadry no this will not guarantee serial order of downloads there must be a serial queue – Shehata Gamal Sep 20 '18 at 10:29
  • @Sh_Khan it seems like you have a misunderstanding about how `DispatchQueue`s and `DispatchGroup`s work when executing async blocks on them. You cannot guarantee serial execution using `DispatchQueue.sync{}` even if the queue is serial unless the block you dispatch using `sync` itself if synchronous. `DispatchGroup`'s `.enter()`, `.leave()` and `.notify()` are the way to serialise async execution. – Dávid Pásztor Sep 20 '18 at 10:32
  • simple demo make a for loop inside it async { // here use SDWebimage } and the images will be downloaded in serial despite sdwebimage is asynchronous , it may take sometime but will prove that to you for say 10 images – Shehata Gamal Sep 20 '18 at 10:35
  • 1
    Well, blow me down. With `group.notify` rather than `group.wait` I turns out that _don't_ need the serial queue. Thank you! – Ashley Mills Sep 20 '18 at 10:36
  • @Sh_Khan use a simple built-in async method such as `URLRequest` or a simple `DispatchQueue.asyncAfter` call and you'll see that you are wrong. The upvotes from people who have actually tested my code speak for themselves. – Dávid Pásztor Sep 20 '18 at 10:37
  • No , the op block service() run inside the queue itself , sdweimage is smart enough to dispatch only if the code is inside a main block , i mean the asynchonous process should run in the same queue also – Shehata Gamal Sep 20 '18 at 10:38
2

Maybe it's just a typo but basically don't run the queue synchronously.

Then instead of wait use notify outside(!) of the loop and print the results within the queue.

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()
    }
}
vadian
  • 274,689
  • 30
  • 353
  • 361
  • The question I meant to ask was indeed "one at at time _sequentially_", but this answers the question I did ask, and it turns out that I don't need to do it sequentially in any case. Thank you. – Ashley Mills Sep 20 '18 at 10:29
0

I d'ont think your main queue is locked, otherwise you would probably have an infinite loading on your app, as if it crashed ( in MacOS that's for sure ).

Here is what worked for me, maybe it will help :

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()
    }
}

If I would say, it may come from the queue.sync instead of queue.async. But I'm not an expert on Asynchronous calls.

Hope it helps

Olympiloutre
  • 2,268
  • 3
  • 28
  • 38