Chris G.
Chris G.

Reputation: 25964

DispatchGroup and possible race condition?

Will the following create a possible race condition?

let syncGroup = DispatchGroup()
var tempData: [Int]
for index in 1...10 {
      syncGroup.enter()
      // Alamofire is being called in the following. 
      // I am leaving out implementation, just to simplify.
      asyncTaskUsingAlamofire(completionHandler: { (data: [Data]) in
         // Possible race condition?
         // Should I use something like `sync()` and how?
         tempData.append(data)
         syncGroup.leave()
      })
}
syncGroup.notify(queue: .main) {
// We can go on, all data is added to tempData
}

I can see that swift has a sync() If and how would you solve this?

Upvotes: 2

Views: 792

Answers (2)

Sulthan
Sulthan

Reputation: 130092

You should make sure that all completion handlers are called on the same queue. If they are not called on the same queue, they can be called simultaneously, creating a race condition.

If you are not sure whether the completion handler is called on a specific queue, you can simply insert an .async wrapper:

asyncTaskUsingAlamofire(completionHandler: { (data: [Data]) in
    DispatchQueue.main.async {
        tempData.append(data)
        syncGroup.leave()
    }
}

However, Alamofire is always executing callbacks on the main queue, therefore no race conditions should happen and your solution is safe.

Upvotes: 1

Luca Angeletti
Luca Angeletti

Reputation: 59496

Serial Queue

Very similar to Sulthan's answer, I just usually use custom serial queue for this scenario

let group = DispatchGroup()
var results = [Int]()
let serialQueue = DispatchQueue(label: "serialQueue")

for _ in 1...10 {
    group.enter()
    asyncTaskUsingAlamofire { data in
        serialQueue.async {
            // Multiple instances of this block will be executed into a serial queue
            // So max 1 block at a time
            // This means results is accessed safely
            results.append(contentsOf: data)
            group.leave()
        }
    }
}

group.notify(queue: .main) {
    print(results)
}

The results array is safely accessed because every instance of the block that mutate it

results.append(contentsOf: data)
group.leave()

is executed inside a Serial Queue.

serialQueue.async {
    results.append(contentsOf: data)
    group.leave()
}

In other words the 10 instances of the block are enqueued into serialQueue and executed one at a time.

This does guarantee a safe access of results.

Upvotes: 4

Related Questions