Wait with RunLoop - How to make it blocking operation that an async operation that calls back to the current queue without deadlocking. - Non deadlock blocking

Updated
Oct 9, 2020 6:07 PM
Created
Oct 8, 2020 2:10 PM
Tags
Swift
Keywords
Date

We use many asynchronous operations in the app. Then if we need to wait it on main-queue, and if that calls back onto main-queue.

func asyncTask(completion: @escaping () -> Void) {

  print("start operation")
  DispatchQueue.global().async {
    print("done operation")
    completion()
  }

}

Ensure that we call this method from main-queue.

let group = DispatchGroup()
group.enter()
asyncTask {
  group.leave()
}
group.wait()

This causes dead-lock. DispatchGroup is waiting for asyncTask's completion on main-queue, but asyncTask's completion can't interrupt to main-queue, because DispatchGroup is waiting for ... Yes, the explanations are loop, that is dead-lock.

So how can we do this without dead-lock?

Updates

from

extension RunLoop {

  public func wait<Result>(
    resultType: Result.Type? = nil,
    perform: @escaping (_ fullfill: @escaping (Result) -> Void) -> Void
  ) -> Result {

    var result: Result!

    let runLoopModeRaw = RunLoop.Mode.default.rawValue._bridgeToObjectiveC()
    let cfRunLoop = getCFRunLoop()

    CFRunLoopPerformBlock(getCFRunLoop(), runLoopModeRaw) {
      perform { r in
        result = r
        CFRunLoopPerformBlock(cfRunLoop, runLoopModeRaw) {
          CFRunLoopStop(cfRunLoop)
        }
        CFRunLoopWakeUp(cfRunLoop)
      }

    }

    CFRunLoopWakeUp(cfRunLoop)

    CFRunLoopRun()

    return result
  }
  
}

Older

extension RunLoop {

  public func wait<Result>(resultType: Result.Type? = nil, perform: (_ fullfill: @escaping (Result) -> Void) -> Void) -> Result {

    var isFinished = false
    var result: Result!

    perform { r in
      result = r
      isFinished = true
    }

    while !isFinished {
      run(mode: .default, before: Date(timeIntervalSinceNow: 0.1))
    }

    return result
  }
}

func _await2() {

  print(1)
  RunLoop.current.wait(resultType: Void.self) { (fulfill) in
    print(2)
    asyncTask {
      print(3)
      fulfill(())
    }
  }
  print(4)

}

func asyncTask(completion: @escaping () -> Void) {

  dispatchPrecondition(condition: .onQueue(DispatchQueue.main))
  print("start operation")
  DispatchQueue.main.asyncAfter(deadline: .now() + 4) {
    print("done operation")
    completion()
  }

}