I'm trying to get a better understanding of queues and how they work. This snippet is to test their behaviour:
- (void)dispatchQueueTest
{
NSLog( @"Begin test on %@ thread", [NSThread isMainThread] ? @"main" : @"other" );
dispatch_semaphore_t s = dispatch_semaphore_create(0);
dispatch_async( dispatch_get_main_queue(), ^{
NSLog( @"Signalling semaphore" );
dispatch_semaphore_signal(s);
});
NSLog( @"Waiting for worker" );
while( dispatch_semaphore_wait( s, DISPATCH_TIME_NOW ) ) {
NSDate* timeout = [NSDate dateWithTimeIntervalSinceNow:10.f];
// Process events on the run loop
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:timeout];
}
dispatch_release(s);
NSLog( @"All sync'd up" );
}
As would be expected it produces this in the log:
Begin test on main thread
Waiting for worker
Signalling semaphore
All sync'd up
What is strange is that if this code is called from, say, - (void)viewDidAppear:(BOOL)animated of a UIViewController, then it changes behaviour. Specifically it deadlocks with the following log:
Begin test on main thread
Waiting for worker
My question is why does NSRunLoop runMode not process the block sent via dispatch_async in this situation but it does in others?
I have a project where I push a PlayerNameEntryViewController
onto a navigation controller. I put a breakpoint in -[PlayerNameEntryViewController viewDidAppear:]
. Here's the stack trace when the breakpoint was hit:
#0 0x0002d3d3 in -[PlayerNameEntryViewController viewDidAppear:] at /Volumes/b/Users/mayoff/t/hotseat2/hotseat2/Home/PlayerNameEntryViewController.m:39
#1 0x00638fbf in -[UIViewController _setViewAppearState:isAnimating:] ()
#2 0x006392d4 in -[UIViewController __viewDidAppear:] ()
#3 0x006395d7 in -[UIViewController _endAppearanceTransition:] ()
#4 0x00648666 in -[UINavigationController navigationTransitionView:didEndTransition:fromView:toView:] ()
#5 0x007ee90e in -[UINavigationTransitionView _notifyDelegateTransitionDidStopWithContext:] ()
#6 0x007eec17 in -[UINavigationTransitionView _cleanupTransition] ()
#7 0x007eec86 in -[UINavigationTransitionView _navigationTransitionDidStop] ()
#8 0x005a2499 in -[UIViewAnimationState sendDelegateAnimationDidStop:finished:] ()
#9 0x005a2584 in -[UIViewAnimationState animationDidStop:finished:] ()
#10 0x00497e00 in CA::Layer::run_animation_callbacks(void*) ()
#11 0x02e86515 in _dispatch_main_queue_callback_4CF ()
#12 0x015fe833 in __CFRunLoopRun ()
#13 0x015fddb4 in CFRunLoopRunSpecific ()
#14 0x015fdccb in CFRunLoopRunInMode ()
#15 0x01acd879 in GSEventRunModal ()
#16 0x01acd93e in GSEventRun ()
#17 0x00571a9b in UIApplicationMain ()
#18 0x00002461 in main at /Volumes/b/Users/mayoff/t/hotseat2/hotseat2/main.m:17
Notice frame #11. It's a call to _dispatch_main_queue_callback_4CF
. That is the function that runs blocks put on the main queue. So viewDidAppear:
was actually called from inside a block that was added to the main queue with dispatch_async
.
The main queue is a serial queue. The definition of a serial queue is a queue that only executes one block at a time. If the queue is executing a block, no other block on that queue can start. So when you run the main run loop recursively, the run loop sees that it's already inside a block running on the main queue and doesn't try to start more blocks. That's why your semaphore-signaling block never runs and your app hangs.
Note that sometimes viewDidAppear:
is called from inside a queued block, and sometimes it's not. You shouldn't rely on either behavior.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With