How to Verify That I am Running on a Given Gcd Queue Without Using Dispatch_Get_Current_Queue()

How can I verify that I am running on a given GCD queue without using dispatch_get_current_queue()?

Assign whatever identifier you want using dispatch_queue_set_specific(). You can then check your identifier using dispatch_get_specific().

Remember that dispatch_get_specific() is nice because it'll start at the current queue, and then walk up the target queues if the key isn't set on the current one. This usually doesn't matter, but can be useful in some cases.

What GCD queue, main or not, am I running on?

Updated answer:

The Apple docs have changed and now say "When called from outside of the context of a submitted block, this function returns the main queue if the call is executed from the main thread. If the call is made from any other thread, this function returns the default concurrent queue." so checking dispatch_get_main_queue() == dispatch_get_current_queue() should work.

Original answer:

Using dispatch_get_main_queue() == dispatch_get_current_queue() won't work. The docs for dispatch_get_current_queue say "When called outside of the context of a submitted block, this function returns the default concurrent queue". The default concurrent queue is not the main queue.

[NSThread isMainThread] should work for what you want. Note that [NSThread isMainThread] can be true for for queues other than the main queue though, e.g., when calling dispatch_sync from the main thread.

How to dispatch_after in the current queue?

The various links in the comments don't say "it's better not to do it." They say you can't do it. You must either pass the queue you want or dispatch to a known queue. Dispatch queues don't have the concept of "current." Blocks often feed from one queue to another (called "targeting"). By the time you're actually running, the "current" queue is not really meaningful, and relying on it can (and historically did) lead to dead-lock. dispatch_get_current_queue() was never meant for dispatching; it was a debugging method. That's why it was removed (since people treated it as if it meant something meaningful).

If you need that kind of higher-level book-keeping, use an NSOperationQueue which tracks its original queue (and has a simpler queuing model that makes "original queue" much more meaningful).

There are several approaches used in UIKit that are appropriate:

  • Pass the call-back dispatch_queue as a parameter (this is probably the most common approach in new APIs). See [NSURLConnection setDelegateQueue:] or addObserverForName:object:queue:usingBlock: for examples. Notice that NSURLConnection expects an NSOperationQueue, not a dispatch_queue. Higher-level APIs and all that.
  • Call back on whatever queue you're on and leave it up to the receiver to deal with it. This is how callbacks have traditionally worked.
  • Demand that there be a runloop on the calling thread, and schedule your callbacks on the calling runloop. This is how NSURLConnection historically worked before queues.
  • Always make your callbacks on one of the well-known queues (particularly the main queue) unless told otherwise. I don't know of anywhere that this is done in UIKit, but I've seen it commonly in app code, and is a very easy approach most of the time.

Alternatives to dispatch_get_current_queue() for completion blocks in iOS 6?

The pattern of "run on whatever queue the caller was on" is appealing, but ultimately not a great idea. That queue could be a low priority queue, the main queue, or some other queue with odd properties.

My favorite approach to this is to say "the completion block runs on an implementation defined queue with these properties: x, y, z", and let the block dispatch to a particular queue if the caller wants more control than that. A typical set of properties to specify would be something like "serial, non-reentrant, and async with respect to any other application-visible queue".

** EDIT **

Catfish_Man put an example in the comments below, I'm just adding it to his answer.

- (void) aMethodWithCompletionBlock:(dispatch_block_t)completionHandler     
{
dispatch_async(self.workQueue, ^{
[self doSomeWork];
dispatch_async(self.callbackQueue, completionHandler);
}
}

dispatch_queue_set_specific vs. getting the current queue

First, I really don't think you meant to make that queue concurrent. dispatch_sync()ing to a concurrent queue is not really going to accomplish much of anything (concurrent queues do not guarantee ordering between blocks running on them). So, the rest of this answer assumes you meant to have a serial queue there. Also, I'm going to answer this in general terms, rather than your specific questions; hopefully that's ok :)

There are two fundamental issues with using dispatch_get_current_queue() this way. One very broad one that can be summarized as "recursive locking is a bad idea", and one dispatch-specific one that can be summarized as "you can and often will have more than one current queue".

Problem #1: Recursive locking is a bad idea

The usual purpose of a private serial queue is to protect an invariant of your code ("invariant" being "something that must be true"). For example, if you're using a queue to guard access to a property so that it's thread-safe, then the invariant is "this property does not have an invalid value" (for example: if the property is a struct, then half the struct could have a new value and half could have the old value, if it was being set from two threads at once. A serial queue forces one thread or the other to finish setting the whole struct before the other can start).

We can infer that for this to make sense, the invariant has to hold when beginning execution of a block on the serial queue (otherwise, it clearly wasn't protected). Once the block has begun executing, it can break the invariant (say, set the property) without fear of messing up any other threads as long as the invariant holds again by the time it returns (in this example, the property has to be fully set).

Summarizing just to make sure you're still following: at the beginning and end of each block on a serial queue, the invariant that queue is protecting must hold. In the middle of each block, it may be broken.

If, inside the block, you call something which tries to use the thing protected by the queue, then you've changed this simple rule to a much much more complicated one: instead of "at the beginning and end of each block" it's "at the beginning, end, and at any point where that block calls something outside of itself". In other words, instead of thinking about your thread-safety at the block level, you now have to examine every individual line of each block.

What does this have to do with dispatch_get_current_queue()? The only reason to use dispatch_get_current_queue() here is to check "are we already on this queue?", and if you're already on the current queue then you're already in the scary situation above! So don't do that. Use private queues to protect things, and don't call out to other code from inside them. You should already know the answer to "am I on this queue?" and it should be "no".

This is the biggest reason dispatch_get_current_queue() was deprecated: to stop people from trying to simulate recursive locking (what I've described above) with it.

Problem #2: You can have more than one current queue!

Consider this code:

dispatch_async(queueA, ^{
dispatch_sync(queueB, ^{
//what is the current queue here?
});
});

Clearly queueB is current, but we're also still on queueA! dispatch_sync causes the work on queueA to wait for the completion of work on queueB, so they're both effectively "current".

This means that this code will deadlock:

dispatch_async(queueA, ^{
dispatch_sync(queueB, ^{
dispatch_sync(queueA, ^{});
});
});

You can also have multiple current queues by using target queues:

dispatch_set_target_queue(queueB, queueA);
dispatch_sync(queueB, ^{
dispatch_sync(queueA, ^{ /* deadlock! */ });
});

What would really be needed here is something like a hypothetical "dispatch_queue_is_synchronous_with_queue(queueA, queueB)", but since that would only be useful for implementing recursive locking, and I've already described how that's a bad idea... it's unlikely to be added.

Note that if you only use dispatch_async(), then you're immune to deadlocks. Sadly, you're not at all immune to race conditions.

checking for equality on dispatch_queue_t

This depends on the queue you're on. In this particular case use:

if ([NSThread isMainThread]) {}

In general, you can use dispatch_get_current_queue() to test which queue your're on. In that case you can use the == operator to do so. To quote the Dispatch Queues page in Apple's Concurrency Programming Guide:

Use the dispatch_get_current_queue function for debugging purposes or
to test the identity of the current queue. Calling this function from
inside a block object returns the queue to which the block was
submitted (and on which it is now presumably running). Calling this
function from outside of a block returns the default concurrent queue
for your application.

Get current dispatch queue?

You do have the option of "dispatch_get_current_queue()", however the iOS 6.1 SDK defines this API with these disclaimers:

"Recommended for debugging and logging purposes only:"

and

"This function is deprecated and will be removed in a future release.".

Here's another related question with some alternatives you can consider if you want code that's future-proof.

Log which queue/thread a method is running on

You can get the current thread with +[NSThread currentThread]. That could have a name property, but if you didn't set one don't rely on it.

Queues are trickier because there are different meanings of "queue". A queue could be an NSOperationQueue, and you can grab its name from +[NSOperationQueue currentQueue] (again, assuming you set it).

Then there are dispatch queues. You can get the current queue with dispatch_get_current_queue(), but be warned that this function will succeed even if called from code that isn't associated with a queue(!). In that case it returns the default background queue Queues are labeled, so you can call dispatch_queue_get_label() and if you created the queue with a label, you will get that.

So basically, yes you can get the queue or thread—with the proviso that all code has an associated dispatch queue even if it isn't code that was dispatched. You can also usually get meaningful names for these threads and queues, which is handy for debugging: but it's your responsibility to name them.



Related Topics



Leave a reply



Submit