CFRunLoopPerformBlock vs dispatch_async
The primary difference here is that CFRunLoopPerformBlock
allows you to specify specific run loop modes in which to execute the block, whereas dispatch_async(dispatch_get_main_queue(),...)
is going to execute in common modes only. Perhaps more apropos to the performance issue you're seeing, CFRunLoopPerformBlock
does not wake up the main thread. From the documentation for CFRunLoopPerformBlock
:
This method enqueues the block only and does not automatically wake up the specified run loop. Therefore, execution of the block occurs the next time the run loop wakes up to handle another input source. If you want the work performed right away, you must explicitly wake up that thread using the CFRunLoopWakeUp function.
In practice, this will usually mean that your block wont be executed until the time the run loop wakes up (i.e. user event occurs, timer fires, run loop source fires, mach message is received, etc.) GCD is not, by design, a run-loop based API; the relationship between the main queue and the main thread run loop is, effectively, an implementation detail. I would expect that implementation to wake up the run loop itself if that were necessary for the main queue to be serviced.
Absent information to the contrary, I strongly suspect this is the source of the difference in performance. I would expect the performance to be similar if you added a call to CFRunLoopWakeUp
immediately after your call to CFRunLoopPerformBlock
.
I sometimes use them together:
dispatch_async(dispatch_get_main_queue(), ^(void) {
CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopDefaultMode, ^{
// stuff
});
});
I use this to send a block to the main thread that will execute without causing 'glitches' when a UIScrollview is scrolling.
I've also been recently using:
CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopDefaultMode, ^{
// stuff
});
in lieu of:
[self performSelector:@selector(myMethod:) withObject:myObject afterDelay:0]
in order to defer execution of code to the next pass through the runloop. This way I don't have to create a special method to contain the code I want to execute and don't have to wrap all of the parameters for execution into a single (id) myObject.
GCD's main queue is a serial queue. So, it can only run a single task at a time. Even if that task runs an inner run loop — for example, runs a modal dialog — then other tasks submitted to the main queue can't run until that has completed.
Tasks submitted using CFRunLoopPerformBlock()
can run whenever the run loop is run in one of the target modes. That includes if the run loop is run from within a task that was submitted using CFRunLoopPerformBlock()
.
Consider the following examples:
CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopCommonModes, ^{
printf("outer task milestone 1\n");
CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopCommonModes, ^{
printf("inner task\n");
});
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
printf("outer task milestone 2\n");
});
produces output like:
outer task milestone 1
inner task
outer task milestone 2
While this:
dispatch_async(dispatch_get_main_queue(), ^{
printf("outer task milestone 1\n");
dispatch_async(dispatch_get_main_queue(), ^{
printf("inner task\n");
});
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
printf("outer task milestone 2\n");
});
produces:
outer task milestone 1
outer task milestone 2
inner task