DispatchQueue.current

hello,

i don't think it is provided by the system already so i'd like to implement a smart version of DispatchQueue.async function - the one that will not reschedule the block if i am already on the queue in question and call the block directly instead in this case.
 
Code Block
extension DispatchQueue {
func asyncSmart(execute: @escaping () -> Void) {
if DispatchQueue.current === self { // ?????
execute()
} else {
async(execute: execute)
}
}
}

the immediate problem is that there is no way to get the current queue (in order to compare it with the queue parameter and do the logic branch).

anyone've been through it and solved this puzzle?

Answered by DTS Engineer in 623167022
The C API actually has a dispatch_get_current_queue routine but it’s not exposed to Swift because it’s deprecated. It’s deprecated for complex reasons (for details, see the dispatch_get_current_queue man page) but the executive summary is that Dispatch’s design makes it impossible to give you the semantics you’re looking for.

Taking a step back, the whole goal of your proposed extension is to take you from an arbitrary context to a specific desired queue, but that goal is troublesome. A given piece of code shouldn’t need to detect this at runtime. You should know at the time of writing the code that you’re running on your desired queue. If you are, you don’t need to do anything. If you’re not, you always need to async over to that queue.

Finally, above I mentioned that you should know at the time of writing that you’re running on the desired queue. You can use a Dispatch precondition to assert that at runtime. For example:

Code Block
class Example {
let queue: DispatchQueue
func someFunc() {
dispatchPrecondition(condition: .onQueue(self.queue))
// … rest of your code …
}
}


Note that this API was specifically crafted to prevent folks from falling into the dispatch_get_current_queue pitfall.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@apple.com"
Accepted Answer
The C API actually has a dispatch_get_current_queue routine but it’s not exposed to Swift because it’s deprecated. It’s deprecated for complex reasons (for details, see the dispatch_get_current_queue man page) but the executive summary is that Dispatch’s design makes it impossible to give you the semantics you’re looking for.

Taking a step back, the whole goal of your proposed extension is to take you from an arbitrary context to a specific desired queue, but that goal is troublesome. A given piece of code shouldn’t need to detect this at runtime. You should know at the time of writing the code that you’re running on your desired queue. If you are, you don’t need to do anything. If you’re not, you always need to async over to that queue.

Finally, above I mentioned that you should know at the time of writing that you’re running on the desired queue. You can use a Dispatch precondition to assert that at runtime. For example:

Code Block
class Example {
let queue: DispatchQueue
func someFunc() {
dispatchPrecondition(condition: .onQueue(self.queue))
// … rest of your code …
}
}


Note that this API was specifically crafted to prevent folks from falling into the dispatch_get_current_queue pitfall.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@apple.com"
thanks Quinn,

as a workaround i exposed
Code Block
dispatch_get_current_queue
to Swift in a form of my own DispatchQueue.current - that will do for now; but the whole business of not exposing this method to Swift (while still having it in C) smells dodgy -- if it is bad indeed - it shall be ditched from C; and if it is not so bad it shall be exposed to Swift.

the rationale in the man page is not convincing and doesn't answer why we do not have this API available even for debugging purposes or in a limited form of "DispatchQueue.isCurrent". in particular this fragment is not convincing: "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 returned queue may have arbitrary policies that may surprise code that tries to schedule work with the queue." as my code doesn't try to make any assumptions about the queue returned (in fact doesn't want to execute any code on it) other than it merely needs to know if the current queue is the one the code wants or different solely to avoid the unnecessary dispatch.
 
Code Block
func myLog(_ text: String) {
if DispatchQueue.global().isCurrent {
// quick path. no need to dispatch to global queue, as i am already on it
log(text)
} else {
// slow path
DispatchQueue.global().async {
log(text)
}
}
}

the assumption here is that it would be inconvenient for a caller (who can be a different developer, or myself in a few months time) to bother or remember calling this method on a global queue.

as a workaround i exposed dispatch_get_current_queue to Swift

Obviously you’re free to code your program however you want but I want to be crystal clear about this, both to you and to anyone else who reads this post:
  • Using dispatch_get_current_queue is a mistake.

  • If you continue down this path you will encounter problems, either now, in some interesting edge cases, or in the future, as the system evolves.

Please don’t do this.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@apple.com"
Quinn, i very much appreciate your answer.

what's your opinion on FB8177251 (a new DispatchQueue.asyncWithShortcut API) and FB8177387 (a new DispatchQueue.isCurrent API), do these have a chance to fly?

sometimes the current queue is unknown (recent example: FB8177438 UITableViewDiffableDateSource.apply callback) - but those can be resolved via a less controversial "Thread.isCurrent" check.

ps. and what trick do i use here to avoid "_" to italic conversion?

what's your opinion on FB8177251 (a new DispatchQueue.asyncWithShortcut API) and FB8177387 (a new DispatchQueue.isCurrent API), do these have a chance to fly?

I think I’ll let the Dispatch team respond to those via the feedback system.

what trick do i use here to avoid "_" to italic conversion?

DevForums uses a flavour of Markdown. You have three ways to prevent it interpreting underscores as italics:
  • Quote them with backslash.

  • Use backticks to put them in code style.

  • Put them in a code block, delimited by triple backticks.



Finally, I want to give you a concrete example of where your model breaks down. Consider this code:

Code Block
import Foundation
let q1 = DispatchQueue(label: "q1")
let q2 = DispatchQueue(label: "q2", target: q1)
func main() {
q2.sync {
dispatchPrecondition(condition: .onQueue(q2))
dispatchPrecondition(condition: .onQueue(q1))
dispatchPrecondition(condition: .onQueue(.main))
print("here")
}
dispatchMain()
}
main()


It prints here, meaning that the ‘current’ queue is simultaneously q1, q2, and .main.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@apple.com"
indeed my version handles your example a bit differently:

            assert(q1.isCurrent) // asserts
            assert(q2.isCurrent) // passes
            assert(DispatchQueue.main.isCurrent) // asserts
            assert(Thread.isMainThread) // passes

in my code and for my own queues (the ones i would ever think of or want dispatching to!) i personally never do the two things from your example: specifying the target queue and dispatching synchronously - with the latter it is (IMHO!) too easy to get a deadlock unless i am very carful.

i personally never do the two things from your example: specifying the target queue and dispatching synchronously

I agree that synchronous dispatch is something to be careful about but specifying a target queue is best practice for Dispatch. I recommend that you watch WWDC 2017 Session 706 Modernizing Grand Central Dispatch Usage.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@apple.com"
i have a reputation of a knowledgeable and somewhat "low level" developer among peers (the type who loves hex, and tools like macsbug, gdb and tcpdump) but i'll tell you that: there are much more many iOS devs in the wild (optimistically 100:1) who would blindly call "DispatchQueue.sync" without a second thought than those who would watch that WWDC video and apply its recipes in practice (like the mentioned queue target). overtime Apple realises these trends and and makes APIs more "stupld friendly" (e.g. performBatchUpdates oddities -> diffable data source -> swiftUI). Dispatch shall cross that bridge as well and eventually will do that, or die and resurrect as something very different. but i digressed )
FWIW, this is the response i've got for FB8177251 (that asked for a new DispatchQueue.asyncWithShortcut API) and i've also got a very similar one for FB8177387 (that asked for a new DispatchQueue.isCurrent API):

Thank you for your feedback, it is noted. Engineering has determined that there are currently no plans to address this issue. We decided to deprecate dispatch_get_current_queue because it is extremely fragile and we strongly believe that it is not a good idea for developers to write code based on this. You should always know at any given time, what execution context your code is running on and not require querying it. In addition, a thread can be on multiple queues at once (target queue hierachies) or simply because it has dispatch_sync()-ed across queues A, B and C. In addition queue hierachies are not necessarily static which means that we cannot guarantee that the result of the APi call, will be valid by the time a decision has been made based on this. As such, this information is not something we want to vend to clients. There is a lot of overhead for dispatch to keep track of this and to provide this information and we strongly believe that this API is something developers can easily misuse from a performance standpoint.

note that while report was not about legalising dispatch_get_current_queue the response was along the line how fragile dispatch_get_current_queue is...

the another bullet point from the answer "a thread can be on multiple queues at once" - would just mean that a.isCurrent == b.isCurrent == c.isCurrent == true at the same time (so if isCurrent was used as a check to circumvent unneeded dispatch the check will pass and the block would be executed directly without dispatch), and similarly "asyncSmart" would "do the right thing" for each of the queues in question (a, b, c), e.g. it would contain the same logic that is currently in dispatchPrecondition / dispatch_assert_queue.

another illustrative example why the ability to get the current queue might be needed.
let's assume the project adheres to these rules:

rule #1 calls are executed either on the main queue or on one of the queues i've created explicitly.
rule #2 there is no sudden queue hopping from call to call, any queue hopping if needed is explicit.
Code Block
func foo(_ url: URL) {
assert(this_runs_either_on_main_queue_or_one_of_the_queues_i_ve_created_explicitly()) /* rule #1 */
/* unfortunately it is not immediate obvious which one
as i do not carry the "queue" prameter from call to call. */
let currentQueue = DispatchQueue.current /* grab this ... */
let opQueue = OperationQueue()
opQueue.underlyingQueue = currentQueue /* ... to use here */
let session = URLSession(configuration: .default, delegate: nil, delegateQueue: opQueue) /* ... to use here */
session.dataTask(with: url) { data, response, error in
dispatchPrecondition(condition: .onQueue(currentQueue))
bar(...) /* ... so rules #1 and #2 are satisfied */
}.resume()
}
func bar(...) {
assert(this_runs_either_on_main_queue_or_one_of_the_queues_i_ve_created_explicitly()) /* rule #1 */
/* unfortunately it is not immediate obvious which one
as i do not carry the "queue" prameter from call to call. */
let currentQueue = DispatchQueue.current /* grab this... */
currentQueue.asyncAfter(deadline: .now() + 2) { /* ... to use here */
dispatchPrecondition(condition: .onQueue(currentQueue))
baz(...) /* ... so rules #1 and #2 are satisfied */
}
}
func baz(...) {
assert(this_runs_either_on_main_queue_or_one_of_the_queues_i_ve_created_explicitly()) /* rule #1 */
/* unfortunately it is not immediate obvious which one
as i do not carry the "queue" prameter from call to call. */
/* do something */
}

carrying the currentQueue parameter from call to call would be too noisy, imho.
DispatchQueue.current
 
 
Q