104

I have a method that accepts a block and a completion block. The first block should run in the background, while the completion block should run in whatever queue the method was called.

For the latter I always used dispatch_get_current_queue(), but it seems like it's deprecated in iOS 6 or higher. What should I use instead?

JOM
  • 8,139
  • 6
  • 78
  • 111
cfischer
  • 24,452
  • 37
  • 131
  • 214
  • why do you say `dispatch_get_current_queue()` is deprecated in iOS 6? the docs say nothing about it – jere Nov 05 '12 at 18:10
  • 3
    The compiler complains about it. Try it. – cfischer Nov 05 '12 at 22:27
  • 4
    @jere Check the header file, it does state that it is depricated – WDUK Nov 06 '12 at 09:37
  • Aside from discussions on what is best practice, I see [NSOperationQueue currentQueue] which may answer the question. Not sure about caveats as to its use. – Matt Jul 22 '14 at 22:30
  • found caveat ------ [NSOperationQueue currentQueue] not same as dispatch_get_current_queue() ----- It sometimes returns null ---- dispatch_async(dispatch_get_global_queue(0, 0), ^{ NSLog(@"q(0,0) is %@", dispatch_get_current_queue()); NSLog(@"cq(0,0) is %@", [NSOperationQueue currentQueue]); }); ----- q(0,0) is cq(0,0) is (null)----- depricated or not dispatch_get_current_queue() seems to be the only solution I see for reporting current queue in all conditions – godzilla Mar 06 '16 at 19:56

7 Answers7

65

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); 
    } 
}
Jacksonkr
  • 31,583
  • 39
  • 180
  • 284
Catfish_Man
  • 41,261
  • 11
  • 67
  • 84
  • 7
    Totally agreed. You can see that Apple always follows this; whenever you want to do something on the main queue you always need to dispatch to the main queue because apple always guarantees that you're on a different thread. Most of the time you're waiting for a long running process to finish fetching/manipulating data and then you can process it in the background right in your completion block and then only stick UI calls into a dispatch block on the main queue. Plus it's always good to follow what Apple sets in terms of expectations since developers will be used to the pattern. – Jack Lawrence Nov 11 '12 at 02:56
  • 1
    great answer.. but i was hoping for at least some sample code to illustrate what you're saying – abbood Mar 30 '13 at 04:32
  • 3
    - (void) aMethodWithCompletionBlock:(dispatch_block_t)completionHandler { dispatch_async(self.workQueue, ^{ [self doSomeWork]; dispatch_async(self.callbackQueue, completionHandler); } } – Catfish_Man Mar 30 '13 at 07:16
  • (For a completely trivial example) – Catfish_Man Mar 30 '13 at 07:17
  • Would still like to know if it is possible even if it is not preferable. – Boon Jul 24 '13 at 15:49
  • 3
    It's not possible in the general case because it's possible (and in fact fairly likely) to be on more than one queue simultaneously, due to dispatch_sync() and dispatch_set_target_queue(). There are some subsets of the general case that are possible. – Catfish_Man Aug 01 '13 at 18:19
  • I prefer to alway callback on the main queue by default for libraries since consumers often forget to dispatch back to the main queue from your private background queue. – Heath Borders Jan 02 '14 at 01:47
  • I see the appeal, but the real solution there is for AppKit and UIKit to assert() their main thread requirements (I've filed radars for both), not for every library to contend with event handling. – Catfish_Man Jan 02 '14 at 02:32
  • Unfortunately, calling on the calling queue is necessary when APIs don't properly handle callbacks on arbitrary threads. By far, the easiest way to avoid such problems is to get the current queue, run your code on the main queue (to avoid thread synchronization issues there), then dispatch the callback onto the original queue. Without dispatch_get_current_queue, you need to write really ugly code using NSThread, additional methods, marshaling all your data through a single parameter, etc. – dgatwood Oct 20 '15 at 18:25
27

This is fundamentally the wrong approach for the API you are describing to take. If an API accepts a block and a completion block to run, the following facts need to be true:

  1. The "block to run" should be run on an internal queue, e.g. a queue which is private to the API and hence entirely under that API's control. The only exception to this is if the API specifically declares that the block will be run on the main queue or one of the global concurrent queues.

  2. The completion block should always be expressed as a tuple (queue, block) unless the same assumptions as for #1 hold true, e.g. the completion block will be run on a known global queue. The completion block should furthermore be dispatched async on the passed-in queue.

These are not just stylistic points, they're entirely necessary if your API is to be safe from deadlocks or other edge-case behavior that WILL otherwise hang you from the nearest tree someday. :-)

jkh
  • 3,246
  • 16
  • 13
  • 12
    Sounds reasonable, but for some reason that's not the approach taken by Apple for their own APIs: most methods that take a completion block don't also take a queue... – cfischer Nov 09 '12 at 10:50
  • 2
    True, and to modify my previous assertion somewhat, if it's manifestly obvious that the completion block will be run on the main queue or a global concurrent queue. I'll change my answer to indicate as much. – jkh Nov 11 '12 at 02:26
  • To comment on Apple not taking that approach: Apple is not always "right" per definition. Proper arguments are always stronger than any particular authority, which any scientist will confirm. I think the answer above states it very well from a proper software architecture perspective. – Werner Altewischer Sep 20 '18 at 14:08
15

The other answers are great, but for the me the answer is structural. I have a method like this that's on a Singleton:

- (void) dispatchOnHighPriorityNonMainQueue:(simplest_block)block forceAsync:(BOOL)forceAsync {
    if (forceAsync || [NSThread isMainThread])
        dispatch_async_on_high_priority_queue(block);
    else
        block();
}

which has two dependencies, which are:

static void dispatch_async_on_high_priority_queue(dispatch_block_t block) {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), block);
}

and

typedef void (^simplest_block)(void); // also could use dispatch_block_t

That way I centralize my calls to dispatch on the other thread.

Dan Rosenstark
  • 68,471
  • 58
  • 283
  • 421
12

You should be careful about your use of dispatch_get_current_queue in the first place. From the header file:

Recommended for debugging and logging purposes only:

The code must not make any assumptions about the queue returned, unless it is one of the global queues or a queue the code has itself created. The code must not assume that synchronous execution onto a queue is safe from deadlock if that queue is not the one returned by dispatch_get_current_queue().

You could do either one of two things:

  1. Keep a reference to the queue you originally posted on (if you created it via dispatch_queue_create), and use that from then on.

  2. Use system defined queues via dispatch_get_global_queue, and keep a track of which one you're using.

Effectively whilst previously relying on the system to keep track of the queue you are on, you are going to have to do it yourself.

WDUK
  • 18,870
  • 3
  • 64
  • 72
  • 16
    How can we "keep a reference to the queue you originally posted on" if we cannot use `dispatch_get_current_queue()` to find out which queue that is? Sometimes the code that needs to know which queue it's running on does not have any control or knowledge of it. I have a lot of code that can (and should) be executed on a background queue but occasionally needs to update the gui (progress bar, etc), and therefore needs to dispatch_sync() over to the main queue for those operations. If already on the main queue, dispatch_sync() will lock forever. It'll take me months to refactor my code for this. – Abhi Beckert Dec 30 '12 at 03:42
  • 3
    I think NSURLConnection gives its completion callbacks on the same thread it is called from. Would it be using the same API "dispatch_get_current_queue" to store the queue it is called from to be used at the time of the callback? – defactodeity May 15 '13 at 03:21
5

Apple had deprecated dispatch_get_current_queue(), but left a hole in another place, so we still able to get current dispatch queue:

if let currentDispatch = OperationQueue.current?.underlyingQueue {
    print(currentDispatch)
    // Do stuff
}

This works for main queue at least. Note, that underlyingQueue property is available since iOS 8.

If you need to perform the completion block in the original queue, you also may use OperationQueue directly, I mean without GCD.

Dan Rosenstark
  • 68,471
  • 58
  • 283
  • 421
kelin
  • 11,323
  • 6
  • 67
  • 104
4

For those who still need in queue comparing, you could compare queues by their label or specifies. Check this https://stackoverflow.com/a/23220741/1531141

Community
  • 1
  • 1
alexey.metelkin
  • 1,309
  • 1
  • 11
  • 20
0

This is a me too answer. So I will talk about our use case.

We have a services layer and the UI layer (among other layers). The services layer runs tasks in the background. (Data manipulation tasks, CoreData tasks, Network calls etc). The service layer has a couple operation queues to satisfy the needs of the UI layer.

The UI layer relies on the services layer to do its work and then run a success completion block. This block can have UIKit code in it. A simple use case is to get all messages from the server and reload the collection view.

Here we guarantee that the blocks that are passed into the services layer are dispatched on the queue on which the service was invoked on. Since dispatch_get_current_queue is a deprecated method, we use the NSOperationQueue.currentQueue to get the caller's current queue. Important note on this property.

Calling this method from outside the context of a running operation typically results in nil being returned.

Since we always invoke our services on a known queue (Our custom queues and Main queue) this works well for us. We do have cases where serviceA can call serviceB which can call serviceC. Since we control where the first service call is being made from, we know the rest of the services will follow the same rules.

So NSOperationQueue.currentQueue will always return one of our Queues or the MainQueue.

Kris Subramanian
  • 1,850
  • 18
  • 15