I'm currently building a graph like framework where each node has a dispatch queue. To avoid possible context switching I'd like to set the target of a descendant node to the furthest direct ancestor node with similar dispatch queue context. The graph itself is tree-alike and avoids strong reference cycles by design. However I cannot proceed with that optimization because the setTarget(queue:) method always crashes for custom queues, even in the following simple example:
import Dispatch
DispatchQueue.main.setTarget(queue: DispatchQueue.main) // Okay
DispatchQueue.global().setTarget(queue: DispatchQueue.global()) // Okay
DispatchQueue.global(qos: .utility).setTarget(queue: DispatchQueue.global(qos: .background)) // Okay
let a = DispatchQueue(label: "a", attributes: .concurrent)
a.async {}
let b = DispatchQueue(label: "b", target: a) // Okay
b.async {}
let c = DispatchQueue(label: "c")
c.setTarget(queue: a) // Crashes
// or
c.setTarget(queue: c) // Also crashes (but it worked above with the main queue)
/*
error: Execution was interrupted, reason: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0).
The process has been left at the point where it was interrupted, use "thread return -x" to return to the state before expression evaluation.
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
* frame #0: 0x0000000108b72730 libdispatch.dylib`_dispatch_lane_set_target_queue + 404
frame #1: 0x00007f8660304800
frame #2: 0x000000011e731742 $__lldb_expr20`main at Untitled Page 2.xcplaygroundpage:8
frame #3: 0x000000010594f600 Test`linkResources + 304
frame #4: 0x00000001071c5a3c CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__ + 12
frame #5: 0x00000001071c51f0 CoreFoundation`__CFRunLoopDoBlocks + 336
frame #6: 0x00000001071bfa64 CoreFoundation`__CFRunLoopRun + 1284
frame #7: 0x00000001071bf221 CoreFoundation`CFRunLoopRunSpecific + 625
frame #8: 0x000000010f07f1dd GraphicsServices`GSEventRunModal + 62
frame #9: 0x000000010aeb6115 UIKitCore`UIApplicationMain + 140
frame #10: 0x000000010594f6cd Test`main + 205
frame #11: 0x0000000108bdc551 libdyld.dylib`start + 1
frame #12: 0x0000000108bdc551 libdyld.dylib`start + 1
*/
Is this a bug or intended behavior? Can someone please explain if it should be possible to set the target at runtime and also potentially re-set or remove the target queue entirely?
Alexey
(Alexey Kravchenko)
2
Hello. Maybe it will help - from queue.h:
- In general, changing the target queue of an object is an asynchronous
- operation that doesn't take effect immediately, and doesn't affect blocks
- already associated with the specified object.
-
- However, if an object is inactive at the time dispatch_set_target_queue() is
- called, then the target queue change takes effect immediately, and will
- affect blocks already associated with the specified object. After an
- initially inactive object has been activated, calling
- dispatch_set_target_queue() results in an assertion and the process being
- terminated.
-
- If a dispatch queue is active and targeted by other dispatch objects,
- changing its target queue results in undefined behavior.
It kind of makes a little sense, but why does it work with global queues?!
Alexey
(Alexey Kravchenko)
4
One more interesting comment for dispatch_queue_attr_make_with_autorelease_frequency:
- The global concurrent queues have the DISPATCH_AUTORELEASE_FREQUENCY_NEVER
- behavior. Manually created dispatch queues use
- DISPATCH_AUTORELEASE_FREQUENCY_INHERIT by default.
-
- Queues created with this attribute cannot change target queues after having
- been activated. See dispatch_set_target_queue() and dispatch_activate().
let a = DispatchQueue(label: "a", attributes: .concurrent)
a.async {}
let b = DispatchQueue(label: "b", autoreleaseFrequency: .never)
b.setTarget(queue: a)
Still crashes
That part is still confusing to me. I might need to create a pool of queues where the custom queues will always reference to during the init call to work around the problem.
Alexey
(Alexey Kravchenko)
6
I agree, it is still unclear. May be you can try to create queue inactive via
dispatch_queue_attr_make_initially_inactive
For anyone else seeing this, just wanted to mention that the final recommendation, i.e. mark as initiallyInactive, works, but that probably the best way to set a target is in the init method itself:
let queue = DispatchQueue(label: "com.me.queue", target: .global(qos: .default))
If you don't know the target at creation time, you can set the queue to be initially inactive, then set that target later (and don't forget to activate!)
let queue = DispatchQueue(label: "com.me.queue", attributes: .initiallyInactive)
// Some time later
queue.setTarget(queue: .main)
queue.activate()
1 Like