Alternatives to dispatch_get_current_queue() for c

2019-01-06 10:08发布

问题:

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?

回答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); 
    } 
}


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



回答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.



回答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.



回答5:

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



回答6:

Apple had deprecated dispatch_get_current_queue(), but left a hole in other 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.



回答7:

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.