Josh Paradroid
Josh Paradroid

Reputation: 1414

How can you use Dispatch Groups to wait to call multiple functions that depend on different data?

I have three variables, a, b and c. I have three asynchronous functions with completion blocks to update these variables and three more functions that do some work with only some of the data.

I'm making sure that the working functions wait until all the data is updated with a DispatchGroup.

// The Data
var a: String?
var b: String?
var c: String?

// The Update
let group = DispatchGroup()

group.enter()
updateA() {
    group.leave()
}

group.enter()
updateB() {
    group.leave()
}

group.enter()
updateC() {
    group.leave()
}

group.wait()

// The work
doSomthingWith(a: a, b: b)
doSomethingElseWith(b: b, c: c)
doAnotherThingWith(a: a, c: c)

What I'd like to be able to do is call each work function once the parameters have been updated, rather than waiting for everything. This is only a (obviously) simplified version. There could be many more variables and functions.

I'm using Swift. Many thanks in advance.

Upvotes: 2

Views: 3803

Answers (1)

Martin R
Martin R

Reputation: 539765

To achieve that with dispatch groups alone you would need three dispatch groups which are entered and left accordingly:

let abGroup = DispatchGroup()
let bcGroup = DispatchGroup()
let acGroup = DispatchGroup()

abGroup.enter()
abGroup.enter()
bcGroup.enter()
bcGroup.enter()
acGroup.enter()
acGroup.enter()

// When a is updated:
abGroup.leave()
acGroup.leave()

// When b is updated:
abGroup.leave()
bcGroup.leave()

// When c is updated:
acGroup.leave()
bcGroup.leave()

Then you can wait for the completion of each group independently

abGroup.notify(queue: .main) {
    // Do something with a and b
}
bcGroup.notify(queue: .main) {
    // Do something with b and c
}
acGroup.notify(queue: .main) {
    // Do something with a and c
}

However, this does not scale well with more tasks and dependencies.

The better approach is to add Operations to an OperationQueue, that allows to add arbitrary dependencies:

let queue = OperationQueue()

let updateA = BlockOperation {
    // ...
}
queue.addOperation(updateA)

let updateB = BlockOperation {
    // ...
}
queue.addOperation(updateB)

let updateC = BlockOperation {
    // ...
}
queue.addOperation(updateC)

let doSomethingWithAandB = BlockOperation {
    // ...
}
doSomethingWithAandB.addDependency(updateA)
doSomethingWithAandB.addDependency(updateB)
queue.addOperation(doSomethingWithAandB)

let doSomethingWithBandC = BlockOperation {
    // ...
}
doSomethingWithBandC.addDependency(updateB)
doSomethingWithBandC.addDependency(updateC)
queue.addOperation(doSomethingWithBandC)

let doSomethingWithAandC = BlockOperation {
    // ...
}
doSomethingWithAandC.addDependency(updateA)
doSomethingWithAandC.addDependency(updateC)
queue.addOperation(doSomethingWithAandC)

For asynchronous request with completion handlers you can use a (local) dispatch group inside each block operation to wait for the completion.

Here is a self-contained example:

import Foundation

var a: String?
var b: String?
var c: String?

let queue = OperationQueue()

let updateA = BlockOperation {
    let group = DispatchGroup()
    group.enter()
    DispatchQueue.global().asyncAfter(deadline: .now() + 1.0, execute: {
        a = "A"
        group.leave()
    })
    group.wait()
    print("updateA done")
}
queue.addOperation(updateA)

let updateB = BlockOperation {
    let group = DispatchGroup()
    group.enter()
    DispatchQueue.global().asyncAfter(deadline: .now() + 2.0, execute: {
        b = "B"
        group.leave()
    })
    group.wait()
    print("updateB done")
}
queue.addOperation(updateB)

let updateC = BlockOperation {
    let group = DispatchGroup()
    group.enter()
    DispatchQueue.global().asyncAfter(deadline: .now() + 3.0, execute: {
        c = "C"
        group.leave()
    })
    group.wait()
    print("updateC done")
}
queue.addOperation(updateC)

let doSomethingWithAandB = BlockOperation {
    print("a=", a!, "b=", b!)
}
doSomethingWithAandB.addDependency(updateA)
doSomethingWithAandB.addDependency(updateB)
queue.addOperation(doSomethingWithAandB)

let doSomethingWithAandC = BlockOperation {
    print("a=", a!, "c=", c!)
}
doSomethingWithAandC.addDependency(updateA)
doSomethingWithAandC.addDependency(updateC)
queue.addOperation(doSomethingWithAandC)

let doSomethingWithBandC = BlockOperation {
    print("b=", b!, "c=", c!)
}
doSomethingWithBandC.addDependency(updateB)
doSomethingWithBandC.addDependency(updateC)
queue.addOperation(doSomethingWithBandC)

queue.waitUntilAllOperationsAreFinished()

Output:

updateA done
updateB done
a= A b= B
updateC done
a= A c= C
b= B c= C

Upvotes: 9

Related Questions