When you call operationQueue.waitUntilAllOperationsAreFinished()
on the main thread it will be blocked, and delegates or other code scheduled from other threads onto the main thread will cause a deadlock.
This is one of the rare cases where you have to "busy wait" and poll (thread-safe!) some sort of flag which indicates that a task has been completed. Fortunately, you can leverage a RunLoop
without stressing the CPU too much. With a run loop doing the "wait" on the main thread you can also actually execute delegates or continuations scheduled on the main thread without running into a deadlock.
The following code (using Swift) shows how you can use a run loop to accomplish the "busy wait" on the main thread and also a task which executes on the main thread as well:
Suppose you have some "future" which represents the eventual result of an asynchronous task:
public protocol Future : class {
var isCompleted: Bool { get }
func onComplete(f: ()->())
}
Ideally, you can "register" (one or more) completion handler(s) which get invoked when the future completes.
Now we can define a method runloopWait
for the future which "waits" on a thread (which must have a RunLoop) up until the future is completed:
extension Future {
public func runLoopWait() {
// The current thread MUST have a run loop and at least one event source!
// This is difficult to verfy in this method - thus this is simply
// a prerequisite which must be ensured by the client. If there is no
// event source, the run lopp may quickly return with the effect that the
// while loop will "busy wait".
var context = CFRunLoopSourceContext()
let runLoop = CFRunLoopGetCurrent()
let runLoopSource = CFRunLoopSourceCreate(nil, 0, &context)
CFRunLoopAddSource(runLoop, runLoopSource, kCFRunLoopDefaultMode)
self.onComplete() {
CFRunLoopStop(runLoop);
}
while !self.isCompleted {
CFRunLoopRun()
}
CFRunLoopRemoveSource(runLoop, runLoopSource, kCFRunLoopDefaultMode)
}
}
With this code, the run loop only ever returns when the completion handler has been invoked. That is, you actually don't have to busy wait, rather it's some sort of async wait.
The code below completes the sample with mocking the future and which you can run to examine the solution above. Note, that the implementation of the future is not thread-safe, but should work in this example.
public class Task : Future {
var _completed = false
let _task: () -> ()
var _continuation: ()->()
public init(task: () -> ()) {
_task = task
_continuation = {}
}
public var isCompleted: Bool {
return _completed
}
public static func start(t: Task, queue: dispatch_queue_t ) {
dispatch_async(queue) {
t._task()
t._completed = true
t._continuation()
}
}
public func onComplete(f:()->()) {
_continuation = f
}
}
let t = Task() {
for _ in 0...5 {
sleep(1)
print(".", terminator: "")
}
print("")
}
// run the task on the main queue:
Task.start(t, queue: dispatch_get_main_queue())
t.runLoopWait() // "wait" on the main queue
print("main thread finished")