EvanTsai EvanTsai - 13 days ago 4x
iOS Question

How to handle group wait result in Swift 3

I was trying following code in playground, but it seems that they are not working as I expected.

Two group_async operations cause about 5~6 seconds in total on my mac.

  • When I set the timeout time to DispatchTime.now() + 10, "test returns" and "done" are both printed.

  • When I set the timeout time to DispatchTime.now() + 1 (some value make the group timed out), nothing is printed except the printing codes in two group_async operations.

What I want is to suspend the group and do some clean-up when timed out, and do some other further operations when group successfully finished. Any advice is appreciated. Thanks.

import Dispatch
import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true
let queue = DispatchQueue.global(qos: .utility)

func test() {
let group = DispatchGroup()
__dispatch_group_async(group, queue) {
var a = [String]()
for i in 1...999 {
print("appending array a...")
print("a finished")

__dispatch_group_async(group, queue) {
var b = [String]()
for i in 1...999 {
print("appending array b...")
print("b finished")

let result = group.wait(timeout: DispatchTime.now() + 10)
if result == .timedOut {
print("timed out")
print("test returns")

queue.async {

Rob Rob

This code snippet raises a variety of different questions:

  1. I notice that the behavior differs a bit between the playground and when you run it in an app. I suspect it's some idiosyncrasy of needsIndefiniteExecution of PlaygroundPage and GCD. I'd suggest testing this in an app. With the caveat of the points I raise below, it works as expected when I ran this from an app.

  2. I notice that you've employed this pattern:

    __dispatch_group_async(group, queue) {

    I would suggest:

    queue.async(group: group) {
  3. You are doing group.suspend(). A couple of caveats:

    • One suspends queues, not groups.

    • And if you ever call suspend(), make sure you have a corresponding call to resume() somewhere.

    • Also, remember that suspend() stops future blocks from starting, but it doesn't do anything with the blocks that may already be running. If you want to stop blocks that are already running, you may want to cancel them.

    • Finally, make sure that you only suspend queues and sources that you create. Don't ever try to suspend a global queue.

  4. I also notice that you're using wait on the same queue that you dispatched the test() call. In this case, you're OK because it's a concurrent queue, but this sort of pattern invites deadlocks. I'd suggest avoiding wait altogether, if you can, and certainly don't do it on the same queue that you called it from. Again, it's not a problem here, but it's a pattern that might get you in trouble in the future.

    Personally, I might be inclined to use notify rather than wait to trigger the block of code to run when the two dispatched blocks are done. This eliminates any deadlock risk. And if I wanted to have a block of code to run after a certain amount of time (i.e. a timeout process), I might use asyncAfter (see How do I write dispatch_after GCD in Swift 3?) or a Timer to trigger some cleanup process in case those two blocks were still running (perhaps canceling them; see How to stop a DispatchWorkItem in GCD?).