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

54,424

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:

  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. :-)

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:

  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.

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.

Share:
54,424

Related videos on Youtube

cfischer
Author by

cfischer

Updated on February 22, 2022

Comments

  • cfischer
    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
      jere over 11 years
      why do you say dispatch_get_current_queue() is deprecated in iOS 6? the docs say nothing about it
    • cfischer
      cfischer over 11 years
      The compiler complains about it. Try it.
    • WDUK
      WDUK over 11 years
      @jere Check the header file, it does state that it is depricated
    • Matt
      Matt almost 10 years
      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.
    • godzilla
      godzilla about 8 years
      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 <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
    cfischer over 11 years
    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...
  • jkh
    jkh over 11 years
    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.
  • Jack Lawrence
    Jack Lawrence over 11 years
    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.
  • Abhi Beckert
    Abhi Beckert over 11 years
    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.
  • abbood
    abbood about 11 years
    great answer.. but i was hoping for at least some sample code to illustrate what you're saying
  • Catfish_Man
    Catfish_Man about 11 years
    - (void) aMethodWithCompletionBlock:(dispatch_block_t)completionHandl‌​er { dispatch_async(self.workQueue, ^{ [self doSomeWork]; dispatch_async(self.callbackQueue, completionHandler); } }
  • Catfish_Man
    Catfish_Man about 11 years
    (For a completely trivial example)
  • defactodeity
    defactodeity almost 11 years
    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?
  • Boon
    Boon almost 11 years
    Would still like to know if it is possible even if it is not preferable.
  • Catfish_Man
    Catfish_Man over 10 years
    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.
  • user963601
    user963601 over 10 years
    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.
  • Catfish_Man
    Catfish_Man over 10 years
    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.
  • dgatwood
    dgatwood over 8 years
    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.
  • Werner Altewischer
    Werner Altewischer over 5 years
    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.