Dispatch group


My notes on DispatchGroup

DispatchGroup (pros):

  • Dispatch groups must enter and leave inside async calls on the bg thread.
  • Calls must have the same amount of enter and leaves.
  • I suppose one could pass dispatch group refs as a variable and use it in a nested scenario.
  • DispatchGroups seems to work best when wanting to do many async tasks on background thread at the same time and have one onComplete for when all tasks complete.
  • DispatchGroup can also be used in conjunction with DispatchWorkItem

Cons:

  • cant cancel items (maybe you can if you call .leave()?)
  • does not support retry?
import Foundation
@testable import Utils

// do 3 things async,
// then in each 3 things do 2 things async but do something on main-thread when these 4 things are all finished

class ASyncTest {
   var bg = { return DispatchQueue.global(qos: DispatchQoS.QoSClass.background) }()/*Convenience*/
	var main = { return DispatchQueue.main } () // Convenience
    /**
     * Next implement the bellow in your example:
     * - Todo: Also research blocks
     */
    init(){
            let group = DispatchGroup()
            group.enter()
            bg.async { // do 2 things at the same time
                Swift.print("do default")
                sleep(IntParser.random(3, 6).uint32) // simulates task that takes between 1 and 6 secs
                group.leave()
            }
            if("" != "") { // false
                group.enter()
                bg.async { // do 2 things at the same time
                    Swift.print("do the first")
                    sleep(IntParser.random(2, 7).uint32) // simulates task that takes between 1 and 6 secs
                    group.leave()
                }
            } else { // true
                Swift.print("do the second")
            }

            // group.wait()/*wait blocks main thread, blocks UI, Its important that the notify comes after all enter and leaves has been assigned*/
            // notify also fires when nothing left or entered
            group.notify(queue: main, execute: { // you have to jump back on main thread to call things on main thread as this scope is still on bg thread
                    Swift.print("🏁 group completed: 🏁") // make a method on mainThread and call that instead.
            })
    }
}


DispatchGroup and DispatchWorkItem

Threading 1 0n 1:

Awesome guy:http://stackoverflow.com/users/4665907/that-lazy-ios-guy-웃 Made a 15min video about Threading in swift 3 just for me:

Simpler example:

Use DispatchGroups to archive this. You can either get notified when the groups enter() and leave() calls are balanced:

func myFunction() {
    var a: Int?
    let group = DispatchGroup()
    group.enter()
    DispatchQueue.main.async {
        a = 1
        group.leave()
    }
    // Does not wait. But the code in notify() is run
    // After enter() and leave() calls are balanced
    group.notify(queue: .main) {
        print(a)
    }
}

Or you can wait (and return):

func myFunction() -> Int? {
    var a: Int?
    let group = DispatchGroup()
    group.enter()
    DispatchQueue.global(attributes: .qosDefault).async { // avoid deadlocks by not using .main queue here
        a = 1
        group.leave()
    }
    group.wait() // wait ...
    return a // ... and return as soon as "a" has a value
}

Another example:

// First, we create a group to synchronize our tasks
let group = DispatchGroup()
// NoteCollection is a thread-safe collection class for storing notes
let collection = NoteCollection()
// The 'enter' method increments the group's task count…
group.enter()
localDataSource.load { notes in
    collection.add(notes)
    // …while the 'leave' methods decrements it
    group.leave()
}
group.enter()
iCloudDataSource.load { notes in
    collection.add(notes)
    group.leave()
}
group.enter()
backendDataSource.load { notes in
    collection.add(notes)
    group.leave()
}
// This closure will be called when the group's task count reaches 0
group.notify(queue: .main) { [weak self] in
    self?.render(collection)
}

Looping an array with DispatchGroup

extension Array where Element == DataSource {
    func load(completionHandler: @escaping (NoteCollection) -> Void) {
        let group = DispatchGroup()
        let collection = NoteCollection()

        // De-duplicate the synchronization code by using a loop
        for dataSource in self {
            group.enter()
            dataSource.load { notes in
                collection.add(notes)
                group.leave()
            }
        }

        group.notify(queue: .main) {
            completionHandler(collection)
        }
    }
}
let dataSources: [DataSource] = [
    localDataSource,
    iCloudDataSource,
    backendDataSource
]

dataSources.load { [weak self] collection in
    self?.render(collection)
}

DispatchGroup and array

func executeMultiTask() {
     //1. Create group
     let taskGroup = DispatchGroup()
     //2. Enter group
     taskGroup.enter()
     myTask1.execute(completeHandler: {
         // ...
         //3. Leave group
         taskGroup.leave() //< balance with taskGroup.enter()
     })
     /* Add more tasks ...
     //2. Enter group
     taskGroup.enter()
     myTask2.execute(completeHandler: {
         //3. Leave group
         defer {
            // Use `defer` to make sure, `leave()` calls are balanced with `enter()`.
            taskGroup.leave()
         }
         // ... more
     })
     */
     //4. Notify when all task completed at main thread queue.
     taskGroup.notify(queue: .main) {
         // All tasks are done.
         // ...   
     }
}