Alternatives to dispatch_get_current_queue() for completion blocks in iOS 6?
Solution 1
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);
}
}
Solution 2
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:
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.
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. :-)
Solution 3
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.
Solution 4
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:
Keep a reference to the queue you originally posted on (if you created it via
dispatch_queue_create
), and use that from then on.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.
Solution 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.
Related videos on Youtube
cfischer
Updated on February 22, 2022Comments
-
cfischer about 2 years
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?-
jere over 11 yearswhy do you say
dispatch_get_current_queue()
is deprecated in iOS 6? the docs say nothing about it -
cfischer over 11 yearsThe compiler complains about it. Try it.
-
WDUK over 11 years@jere Check the header file, it does state that it is depricated
-
Matt almost 10 yearsAside from discussions on what is best practice, I see [NSOperationQueue currentQueue] which may answer the question. Not sure about caveats as to its use.
-
godzilla about 8 yearsfound 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 <OS_dispatch_queue_root: com.apple.root.default-qos[0x100195140]> 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
-
-
cfischer over 11 yearsSounds 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...
-
jkh over 11 yearsTrue, 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.
-
Jack Lawrence over 11 yearsTotally 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.
-
Abhi Beckert over 11 yearsHow 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. -
abbood about 11 yearsgreat answer.. but i was hoping for at least some sample code to illustrate what you're saying
-
Catfish_Man about 11 years- (void) aMethodWithCompletionBlock:(dispatch_block_t)completionHandler { dispatch_async(self.workQueue, ^{ [self doSomeWork]; dispatch_async(self.callbackQueue, completionHandler); } }
-
Catfish_Man about 11 years(For a completely trivial example)
-
defactodeity almost 11 yearsI 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?
-
Boon almost 11 yearsWould still like to know if it is possible even if it is not preferable.
-
Catfish_Man over 10 yearsIt'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.
-
user963601 over 10 yearsI 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.
-
Catfish_Man over 10 yearsI 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.
-
dgatwood over 8 yearsUnfortunately, 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.
-
Werner Altewischer over 5 yearsTo 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.