Get current dispatch queue?
Asked Answered
D

12

68

I have a method which should support being called from any queue, and should expect to.

It runs some code in a background thread itself, and then uses dispatch_get_main_queue when it returns a value to its block argument.

I don't want it to force it onto the main queue if it wasn't when it entered the method. Is there a way to get a pointer to the current dispatch queue?

Dia answered 4/7, 2013 at 16:38 Comment(2)
For Swift, have a look at https://mcmap.net/q/176604/-how-to-get-the-current-queue-name-in-swift-3Nicky
forums.swift.org/t/…Rabbi
C
27

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.

Cylindroid answered 4/7, 2013 at 16:41 Comment(1)
This has been deprecated and should not removed. Replacement would be dispatchPrecondition.Regularly
B
36

If you are working with an NSOperationQueue, it can provide the current dispatch queue for you.

NSOperationQueue has the class function [NSOperationQueue currentQueue], which returns the current queue as a NSOperationQueue object. To get the dispatch queue object you can use [NSOperationQueue currentQueue].underlyingQueue, which returns your currrent queue as a dispatch_queue_t.

Swift 3:

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

- works for main queue!

Brigette answered 17/4, 2015 at 20:35 Comment(6)
Note that underlyingQueue was added in iOS 8.0.Fancy
I've been playing around with this, and it doesn't seem that you'll get the correct return value for [NSOperationQueue currentQueue] if your code is executing within a GCD queue not associated with an NSOperationQueue. In other words, if I execute a block in a GCD queue directly and I call [NSOperationQueue currentQueue].underlyingQueue from within that block, I never get the same value as the actual queue in which I'm executing the block.Thimbu
I think @Thimbu is right. I did an experiment which running a gcd task block with dispatch_sync(myqueue, ^{}) and the [NSOperationQueue currentQueue] return the main queue.Miquelon
This answer should be updated, as @Thimbu said. OperationQueue.current?.underlyingQueue should be only used within right context.Sherwoodsherwynd
This answer is misleading in that it reads like it should apply for any situation... it does not. It only applies if you're using an OperationQueue, which is NOT true if you're using a DispatchQueue directly. Please update your answer to indicate this.Alphonso
The underlyingQueue property is documented to be only non-nil, if you explicitly assign a dispatch_queue_t.Ferret
R
33

With the deprecation of dispatch_get_current_queue() there is effectively no way to know what queue you're executing on. If you peruse the GCD sources, you'll eventually see that this is because there may be multiple answers to the question "what queue am I executing on?" (Because queues eventually target one of the global queues, etc.)

If you want to guarantee that a future block is run on a specific queue, then the only way is to make your API accept a queue as a parameter along with the completion block. This lets the caller decide where the completion gets executed.

If simply knowing whether the caller is on the main thread or not is enough, you can use +[NSThread isMainThread] to find out. In the common case, all blocks executing on the main GCD queue will be executing on the main thread. (One exception to this rule is if your application uses dispatch_main() in lieu of a main run loop, you will have to use dispatch_get_specific and friends to detect with certainty that you are executing on the main queue -- this is a comparatively rare circumstance.) More commonly, note that not all code that executes on the main thread executes on the main queue via GCD; GCD is subordinate to the main thread runloop. For your specific case it sounds like that might be enough.

Rouault answered 16/7, 2013 at 13:57 Comment(0)
C
27

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.

Cylindroid answered 4/7, 2013 at 16:41 Comment(1)
This has been deprecated and should not removed. Replacement would be dispatchPrecondition.Regularly
S
19

With the deprecation of dispatch_get_current_queue() you cannot directly get a pointer to the queue you are running on, however you do can get the current queue's label by calling dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL) and that does give you some flexibility.

You can always check if you are on that specific queue just by comparing their labels, so in your case if you don't want to force it on main queue, when you entered the method you can just utilize the following flag:

let isOnMainQueue = (dispatch_queue_get_label(dispatch_get_main_queue()) == dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL))

If you are running on the global queue, you will respectfully get the queue's label associated with it's QOS type, which can be one of the following:

com.apple.root.user-interactive-qos //qos_class_t(rawValue: 33)
com.apple.root.user-initiated-qos   //qos_class_t(rawValue: 25)
com.apple.root.default-qos          //qos_class_t(rawValue: 21)  
com.apple.root.utility-qos          //qos_class_t(rawValue: 17)
com.apple.root.background-qos       //qos_class_t(rawValue: 9) 

And then you can use dispatch_get_global_queue(qos_class_self(), 0) which will give you back that same global queue you are running on.

But I believe Apple particularly discourages us from bounding the logic to the queue we got called on, so better utilising this for exclusively debugging purposes.

Swank answered 8/1, 2016 at 20:29 Comment(5)
I found this method is not reliable. According to the document, label is an optional parameter, so it can be NULL. dispatch_queue_get_label() returns an empty string if no label was provided at creation.Zedekiah
that's true, it is just a workaround, as I said - for testing and debugging it might be particularly useful, but binding the logic in the code is not a good idea..Swank
Since the workaround seems the best known method, I have to use it to determine whether the current queue is a specific serial queue. If yes, just call blocks directly instead of calling dispatch_sync which causes dead lock. To avoid the corner case I mentioned before. I just simply reject any queue without a label.Zedekiah
Normally I try to avoid using dispatch_sync where I can. I usually design everything asynch and then callback on the previous dispatch queue. I think it is often good from the design perspective to not having stuff determined on runtime on which queue it should execute. Having the queue based on its specific single purpose is usually the way that I use to design, so I never need to check on which queue I am running.Swank
Good to mention that in Swift 4 this method is not avaialble but with with horible work around you can still get the label extension DispatchQueue { class var currentLabel: String { return String(validatingUTF8: __dispatch_queue_get_label(nil)) ?? "unknown" } }Formularize
P
19

Based on Oleg Barinov answer

Details

  • Swift 5.1, Xcode 11.3.1

Solution

import Foundation

// MARK: private functionality

extension DispatchQueue {
    
    private struct QueueReference { weak var queue: DispatchQueue? }

    private static let key: DispatchSpecificKey<QueueReference> = {
        let key = DispatchSpecificKey<QueueReference>()
        setupSystemQueuesDetection(key: key)
        return key
    }()
    
    private static func _registerDetection(of queues: [DispatchQueue], key: DispatchSpecificKey<QueueReference>) {
        queues.forEach { $0.setSpecific(key: key, value: QueueReference(queue: $0)) }
    }

    private static func setupSystemQueuesDetection(key: DispatchSpecificKey<QueueReference>) {
        let queues: [DispatchQueue] = [
                                        .main,
                                        .global(qos: .background),
                                        .global(qos: .default),
                                        .global(qos: .unspecified),
                                        .global(qos: .userInitiated),
                                        .global(qos: .userInteractive),
                                        .global(qos: .utility)
                                    ]
        _registerDetection(of: queues, key: key)
    }
}
    
// MARK: public functionality

extension DispatchQueue {
    static func registerDetection(of queue: DispatchQueue) {
        _registerDetection(of: [queue], key: key)
    }

    static var currentQueueLabel: String? { current?.label }
    static var current: DispatchQueue? { getSpecific(key: key)?.queue }
}

Usage

Detect system queue

DispatchQueue.currentQueueLabel
DispatchQueue.current
DispatchQueue.global(qos: .default) == DispatchQueue.current
DispatchQueue.main === DispatchQueue.current

Detect custom queue

let queue = DispatchQueue(label: "queue-sample")
DispatchQueue.registerDetection(of: queue)
if DispatchQueue.current == queue { ... }

Sample

func subTest(queue: DispatchQueue) {
    queue.async {
        print("--------------------------------------------------------")
        print("queue label: \(DispatchQueue.currentQueueLabel ?? "nil")")
        print("print DispatchQueue.current: \(String(describing: DispatchQueue.current))")
        print("print queue == DispatchQueue.current: \(queue == DispatchQueue.current)")
        print("print queue === DispatchQueue.current: \(queue === DispatchQueue.current)")
        print("DispatchQueue.main == DispatchQueue.current: \(DispatchQueue.main == DispatchQueue.current)\n")
    }
}

func test() {
    subTest(queue: DispatchQueue.main)
    sleep(1)
    subTest(queue: DispatchQueue.global(qos: .default))
    sleep(1)
    subTest(queue: DispatchQueue.global(qos: .utility))
    sleep(1)

    let queue = DispatchQueue(label: "queue-sample")
    DispatchQueue.registerDetection(of: queue)
    subTest(queue: queue)
    sleep(1)
}

test()
DispatchQueue.global(qos: .default).async {
    test()
}

Sample Output

--------------------------------------------------------
queue label: com.apple.root.default-qos
print DispatchQueue.current: Optional(<OS_dispatch_queue_global: com.apple.root.default-qos[0x7fff89eb47c0] = { xref = -2147483648, ref = -2147483648, sref = 1, target = [0x0], width = 0xfff, state = 0x0060000000000000, in-barrier}>)
print queue == DispatchQueue.current: true
print queue === DispatchQueue.current: true
DispatchQueue.main == DispatchQueue.current: false

--------------------------------------------------------
queue label: com.apple.root.utility-qos
print DispatchQueue.current: Optional(<OS_dispatch_queue_global: com.apple.root.utility-qos[0x7fff89eb46c0] = { xref = -2147483648, ref = -2147483648, sref = 1, target = [0x0], width = 0xfff, state = 0x0060000000000000, in-barrier}>)
print queue == DispatchQueue.current: true
print queue === DispatchQueue.current: true
DispatchQueue.main == DispatchQueue.current: false

--------------------------------------------------------
queue label: queue-sample
print DispatchQueue.current: Optional(<OS_dispatch_queue_serial: queue-sample[0x600000275780] = { xref = 7, ref = 3, sref = 2, target = com.apple.root.default-qos.overcommit[0x7fff89eb4840], width = 0x1, state = 0x0060002500000b01, enqueued, max qos 5, draining on 0xb03, in-barrier}>)
print queue == DispatchQueue.current: true
print queue === DispatchQueue.current: true
DispatchQueue.main == DispatchQueue.current: false

--------------------------------------------------------
queue label: com.apple.main-thread
print DispatchQueue.current: Optional(<OS_dispatch_queue_main: com.apple.main-thread[0x7fff89eb43c0] = { xref = -2147483648, ref = -2147483648, sref = 1, target = com.apple.root.default-qos.overcommit[0x7fff89eb4840], width = 0x1, state = 0x001ffe9000000300, dirty, in-flight = 0, thread = 0x303 }>)
print queue == DispatchQueue.current: true
print queue === DispatchQueue.current: true
DispatchQueue.main == DispatchQueue.current: true

--------------------------------------------------------
queue label: com.apple.main-thread
print DispatchQueue.current: Optional(<OS_dispatch_queue_main: com.apple.main-thread[0x7fff89eb43c0] = { xref = -2147483648, ref = -2147483648, sref = 1, target = com.apple.root.default-qos.overcommit[0x7fff89eb4840], width = 0x1, state = 0x001ffe9000000300, dirty, in-flight = 0, thread = 0x303 }>)
print queue == DispatchQueue.current: true
print queue === DispatchQueue.current: true
DispatchQueue.main == DispatchQueue.current: true

--------------------------------------------------------
queue label: com.apple.root.default-qos
print DispatchQueue.current: Optional(<OS_dispatch_queue_global: com.apple.root.default-qos[0x7fff89eb47c0] = { xref = -2147483648, ref = -2147483648, sref = 1, target = [0x0], width = 0xfff, state = 0x0060000000000000, in-barrier}>)
print queue == DispatchQueue.current: true
print queue === DispatchQueue.current: true
DispatchQueue.main == DispatchQueue.current: false

--------------------------------------------------------
queue label: com.apple.root.utility-qos
print DispatchQueue.current: Optional(<OS_dispatch_queue_global: com.apple.root.utility-qos[0x7fff89eb46c0] = { xref = -2147483648, ref = -2147483648, sref = 1, target = [0x0], width = 0xfff, state = 0x0060000000000000, in-barrier}>)
print queue == DispatchQueue.current: true
print queue === DispatchQueue.current: true
DispatchQueue.main == DispatchQueue.current: false

--------------------------------------------------------
queue label: queue-sample
print DispatchQueue.current: Optional(<OS_dispatch_queue_serial: queue-sample[0x60000027a280] = { xref = 7, ref = 3, sref = 2, target = com.apple.root.default-qos.overcommit[0x7fff89eb4840], width = 0x1, state = 0x0060002500000b01, enqueued, max qos 5, draining on 0xb03, in-barrier}>)
print queue == DispatchQueue.current: true
print queue === DispatchQueue.current: true
DispatchQueue.main == DispatchQueue.current: false
Pepsinogen answered 20/2, 2020 at 6:34 Comment(1)
Your solution is working so far! Thanks. Will update if I encounter anything worth mentioning.Cota
W
8

The main idea is taken from the SQLite.swift source code.

extension DispatchQueue {
    private static let idKey = DispatchSpecificKey<Int>()
    
    var id: Int {
        let value = unsafeBitCast(self, to: Int.self)
        setSpecific(key: Self.idKey, value: value)
        return value
    }
    
    /// Checks if this queue is the place of execution.
    var isCurrent: Bool {
        id == DispatchQueue.getSpecific(key: Self.idKey)
    }
    
    /// Performs synchronized execution avoiding deadlocks.
    func safeSync<T>(flags: DispatchWorkItemFlags? = nil, execute block: () throws -> T) rethrows -> T {
        try isCurrent ? block() : sync(flags: flags ?? [], execute: block)
    }
}
Wallace answered 17/7, 2019 at 11:49 Comment(0)
M
4

If you're only interested in the current QOS, check the value of Thread.current.qualityOfService.

Maeve answered 17/4, 2020 at 12:54 Comment(0)
V
2

As an alternative approach to this NSOBject's method performSelector:withObject:afterDelay: dispatches the call on the current thread's run loop. According to the docs:

This method sets up a timer to perform the aSelector message on the current thread’s run loop.

Obviously I'm suggesting using this with a delay of zero, which, according to the docs again:

Specifying a delay of 0 does not necessarily cause the selector to be performed immediately. The selector is still queued on the thread’s run loop and performed as soon as possible.

Unfortunately it requires exactly one argument, so some workarounds might be needed if your method takes more or less.

One other thing I noted is that this method is not available for protocols, but implementations alone. This is due to this method living in an NSObject category, and not in the NSObject interface (see PS below). This can easily be fixed by casting to id.

PS: Two different NSObjects exist, a protocol and an implementation. Notice NSObject declaration:

@interface NSObject <NSObject> { ... }

It might seem odd, but one is being declared (after @interface) and the other one is a previously declared protocol (between < and >). When declaring a protocol that extends NSObject (ie., @protocol Foo <NSObject>) the protocol inherits the methods from the later, but not the former. Eventually the protocol is implemented by some class that inherits from the NSObject implementation, so all instances inheriting from the NSObject implementation still holds. But I'm getting off topic.

Voluptuary answered 11/3, 2016 at 23:41 Comment(1)
Or you can use self.perform(#selector(myfunc), on: Thread.current, with: nil, waitUntilDone: false) to exec myfunc on the same thread of your queue.Pushy
P
2

There is actually still a way to compare the queue.

When you set up your queue, make sure that you add the label. For my purposes, I have a shared queue that is used for accessing a database to prevent database locking. In my DB.m file I have defined the shared queue function like:

const char *kTransactionQueueLabel = "DB_TRANSACTION_DISPATCH_QUEUE";

+ (dispatch_queue_t)sharedDBTransactionQueue {
    static dispatch_queue_t sharedDBQueue = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedDBQueue = dispatch_queue_create(kTransactionQueueLabel, DISPATCH_QUEUE_SERIAL);
    });

    return sharedDBQueue;
}

The shared db transaction queue is used locally in the file to dispatch all executions to the database. However, there is also a public accessor on this to allow dispatching entire transactions to the database. So internally, if a DB access method is called from within the transaction queue, we need to dispatch internally on a different queue (all synchronous dispatches). So internally, I always dispatch on the proper queue by using the below getter.

/**
 * @description Decide which queue to use - if we are already in a transaction, use the internal access queue, otherwise use the shared transaction queue.
 */
- (dispatch_queue_t)getProperQueueForExecution {
    const char *currentLabel = dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL);
    dispatch_queue_t sharedAccessQueue = [DB sharedDBTransactionQueue];
    if (strcmp(currentLabel, kTransactionQueueLabel) == 0) {
        sharedAccessQueue = [DB sharedInternalDBAccessQueue];
    }

    return sharedAccessQueue;
}

Hopefully this helps. Sorry for the long example. The Gist of it is that you can use

const char *currentLabel = dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL);

to get the label of the current queue and compare to a defined label.

Philosopher answered 8/7, 2016 at 16:50 Comment(0)
L
0

I have the same functional requirements the original post mentions. You should be able to call this async function on any queue, but if called on the main queue, then callback to the user on the main queue. I simply handle it like so:

// cache value for if we should callback on main queue
BOOL callbackOnMT = [NSThread isMainThread];

// ...
// ... do async work...
// ...

if (callbackOnMT && ![NSThread isMainThread]){
    dispatch_async(dispatch_get_main_queue(), ^{
        // callback to user on main queue
        // as they called this function on main queue
        callbackToUser();
    });
}
else{
    // callback to user on our current queue
    // as they called this function on a non-main queue
    callbackToUser();
}
Licence answered 27/11, 2017 at 20:52 Comment(0)
G
0

I found an implementation in the Apple NIOTransportServices framework ; close to the @Oleg's proposal. Basically:

let inQueueKey = DispatchSpecificKey()
let customID = UUID()
let customQueue = DispatchQueue(label: "custom")
customQueue.setSpecific(key: inQueueKey, value: customID)

func isCustomCurrent() -> Bool {
    return DispatchQueue.getSpecific(key: inQueueKey) == customID
}

But, there is an important note about:

Due to limitations in Dispatch's API, this check is pessimistic: there are circumstances where a perfect implementation could return true, but this version will be unable to prove that and will return false. If you need to write an assertion about being in the event loop that must be correct, use SwiftNIO 1.11 or later and call preconditionInEventLoop and assertInEventLoop.

Further detail: The use of DispatchQueue.sync(execute:) to submit a block to a queue synchronously has the effect of creating a state where the currently executing code is on two queues simultaneously - the one which submitted the block, and the one on which the block runs. If another synchronous block is dispatched to a third queue, that block is effectively running all three at once. Unfortunately, libdispatch maintains only one "current" queue at a time as far as DispatchQueue.getSpecific(key:) is concerned, and it's always the one actually executing code at the time. Therefore the queue belonging to the original event loop can't be detected using its queue-specific data. No alternative API for the purpose exists (aside from assertions via dispatchPrecondition(condition:)). Under these circumstances, inEventLoop will incorrectly be false, even though the current code is actually on the loop's queue. The only way to avoid this is to ensure no callers ever use synchronous dispatch (which is impossible to enforce), or to hope that a future version of libdispatch will provide a solution.

Grange answered 19/4, 2023 at 9:24 Comment(0)
F
-1

To get the label of the current queue and compare to a defined label using.

let queueName = dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL)
Folio answered 17/5, 2019 at 5:29 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.