Grand Central Dispatch (GCD) vs. performSelector - need a better explanation
Asked Answered
T

3

49

I've used both GCD and performSelectorOnMainThread:waitUntilDone in my apps, and tend to think of them as interchangeable--that is, performSelectorOnMainThread:waitUntilDone is an Obj-C wrapper to the GCD C syntax. I've been thinking of these two commands as equivalent:

dispatch_sync(dispatch_get_main_queue(), ^{ [self doit:YES]; });


[self performSelectorOnMainThread:@selector(doit:) withObject:YES waitUntilDone:YES];

Am I incorrect? That is, is there a difference of the performSelector* commands versus the GCD ones? I've read a lot of documentation on them, but have yet to see a definitive answer.

Tove answered 7/3, 2011 at 20:54 Comment(2)
withObject:YES would not work and should give you at least a warning. Which might be one advantage of GDC, where you can send arbitrary arguments to a receiver.Isobar
Right, I'd need to wrap that in an NSNumber. But, ignoring that part, anything else that's different? Good point, though.Tove
V
23

performSelectorOnMainThread: does not use GCD to send messages to objects on the main thread.

Here's how the documentation says the method is implemented:

- (void) performSelectorOnMainThread:(SEL) selector withObject:(id) obj waitUntilDone:(BOOL) wait {
  [[NSRunLoop mainRunLoop] performSelector:selector target:self withObject:obj order:1 modes: NSRunLoopCommonModes];
}

And on performSelector:target:withObject:order:modes:, the documentation states:

This method sets up a timer to perform the aSelector message on the current thread’s run loop at the start of the next run loop iteration. The timer is configured to run in the modes specified by the modes parameter. When the timer fires, the thread attempts to dequeue the message from the run loop and perform the selector. It succeeds if the run loop is running and in one of the specified modes; otherwise, the timer waits until the run loop is in one of those modes.

Vestavestal answered 7/3, 2011 at 20:57 Comment(0)
S
71

As Jacob points out, while they may appear the same, they are different things. In fact, there's a significant difference in the way that they handle sending actions to the main thread if you're already running on the main thread.

I ran into this recently, where I had a common method that sometimes was run from something on the main thread, sometimes not. In order to protect certain UI updates, I had been using -performSelectorOnMainThread: for them with no problems.

When I switched over to using dispatch_sync on the main queue, the application would deadlock whenever this method was run on the main queue. Reading the documentation on dispatch_sync, we see:

Calling this function and targeting the current queue results in deadlock.

where for -performSelectorOnMainThread: we see

wait

A Boolean that specifies whether the current thread blocks until after the specified selector is performed on the receiver on the main thread. Specify YES to block this thread; otherwise, specify NO to have this method return immediately.

If the current thread is also the main thread, and you specify YES for this parameter, the message is delivered and processed immediately.

I still prefer the elegance of GCD, the better compile-time checking it provides, and its greater flexibility regarding arguments, etc., so I made this little helper function to prevent deadlocks:

void runOnMainQueueWithoutDeadlocking(void (^block)(void))
{
    if ([NSThread isMainThread])
    {
        block();
    }
    else
    {
        dispatch_sync(dispatch_get_main_queue(), block);
    }
}

Update: In response to Dave Dribin pointing out the caveats section ondispatch_get_current_queue(), I've changed to using [NSThread isMainThread] in the above code.

I then use

runOnMainQueueWithoutDeadlocking(^{
    //Do stuff
});

to perform the actions I need to secure on the main thread, without worrying about what thread the original method was executed on.

Softfinned answered 7/3, 2011 at 22:53 Comment(8)
@Joe - dispatch_sync() to the main thread could still cause a deadlock within your application if something on the main thread was blocked waiting on a value from your background thread (via dispatch_sync() or something else). This is very unlikely, but still possible. It's the standard threading problem of two threads waiting for the other to do something, so nothing gets done.Softfinned
@Joe - Yes, if you're running entirely on a non-main queue or thread, you don't need the code I have above. The deadlock issue I mentioned just now will be a problem with this code, too. That's more of an architectural issue. As far as the comments go, see this Meta question: meta.stackexchange.com/questions/43019/… .Softfinned
Pedantry overflow: You can use dispatch_block_t as the argument type instead the ugly void (^block)(void).Vestavestal
The caveats section says that you are allowed to compare queues for identity checks (what you were doing). What is the caveat you're worried about here?Varian
@Adam - It appears the section has been updated since I wrote that. It used to tell you that identity checks were not guaranteed to work for dispatch_get_current_queue().Softfinned
This has since been added to the caveats section: It is equally unsafe for code to assume that synchronous execution onto a queue is safe from deadlock if that queue is not the one returned by dispatch_get_current_queue().Schiff
In many cases the only situation where something runs not on main thread it's due to asynchronous http request, and the code being executed by the delegate method on response might still be on the other thread. I used this code to check before I call the delegate methods on all my Http requests, and it saved my day... Thanks! (:Kissner
@BradLarson How about just using dispatch_async(dispatch_get_main_queue(), block)? Any problem ?Gloucester
V
23

performSelectorOnMainThread: does not use GCD to send messages to objects on the main thread.

Here's how the documentation says the method is implemented:

- (void) performSelectorOnMainThread:(SEL) selector withObject:(id) obj waitUntilDone:(BOOL) wait {
  [[NSRunLoop mainRunLoop] performSelector:selector target:self withObject:obj order:1 modes: NSRunLoopCommonModes];
}

And on performSelector:target:withObject:order:modes:, the documentation states:

This method sets up a timer to perform the aSelector message on the current thread’s run loop at the start of the next run loop iteration. The timer is configured to run in the modes specified by the modes parameter. When the timer fires, the thread attempts to dequeue the message from the run loop and perform the selector. It succeeds if the run loop is running and in one of the specified modes; otherwise, the timer waits until the run loop is in one of those modes.

Vestavestal answered 7/3, 2011 at 20:57 Comment(0)
G
2

GCD's way is suppose to be more efficient and easier to handle and is only available in iOS4 onwards whereas performSelector is supported in the older and newer iOS.

Gonta answered 7/3, 2011 at 23:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.