Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CFRunLoopPerformBlock vs dispatch_async

I have some calculation work on background thread, after that I need to update the transform of some calayer, I try use

dispatch_async(dispatch_get_main_queue(), ^{calayer.transform = newTransform});

and

CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopCommonModes, ^(void) {calayer.transform = newTransform});

I just thought they are the same, but I found the calayer worked much smooth(maybe?) when using dispatch_async. what is the different about these two functions?

like image 822
Tim Avatar asked Oct 13 '12 09:10

Tim


3 Answers

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.

like image 112
hyperspasm Avatar answered Sep 28 '22 02:09

hyperspasm


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.

like image 19
ipmcc Avatar answered Nov 12 '22 23:11

ipmcc


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
like image 7
Ken Thomases Avatar answered Nov 12 '22 22:11

Ken Thomases