While playing around with custom executors with following snippet:
import Dispatch
actor SampleActor {
var counter: Int = 0
// let queue = DispatchSerialQueue(label: "actorqueue", target: .global(qos: .default))
// nonisolated var unownedExecutor: UnownedSerialExecutor {
// return .init(ordinary: queue)
// }
func printStuff(count: Int) {
counter += 1
sleep(5)
print("Count: \(count) increased couter \(counter)")
}
}
final class DispatchSerialExecutor: TaskExecutor, SerialExecutor {
let queue: DispatchQueue
static let shared: DispatchSerialExecutor = .init(
queue: .init(label: "sharedqueue", target: .global(qos: .default))
)
init(queue: DispatchQueue) {
self.queue = queue
}
func enqueue(_ job: consuming ExecutorJob) {
let unownedJob = UnownedJob(job)
queue.async {
unownedJob
.runSynchronously(
isolatedTo: self.asUnownedSerialExecutor(),
taskExecutor: self.asUnownedTaskExecutor()
)
}
}
func asUnownedSerialExecutor() -> UnownedSerialExecutor {
return .init(ordinary: self)
}
func asUnownedTaskExecutor() -> UnownedTaskExecutor {
return .init(ordinary: self)
}
}
@Test
func actorExecution() async throws {
let act = SampleActor()
await withTaskGroup(of: Void.self) { group in
for count in 0..<5 {
let task = Task(
executorPreference: DispatchSerialExecutor.shared,
priority: count % 2 == 0 ? .background : .high
) {
print("Started task \(count)")
await act.printStuff(count: count)
}
group.addTask { await task.value }
}
await group.waitForAll()
}
}
I noticed the only way to order task execution in the order of their submission (ignoring priority) is using a custom task executor that provides this guarantee.
// default executors
Started task 0
Started task 1
Started task 3
Started task 2
Started task 4
Count: 1 increased couter 1
Count: 0 increased couter 2
Count: 3 increased couter 3
Count: 2 increased couter 4
Count: 4 increased couter 5
// Actor dispatch executor
Started task 1
Started task 3
Started task 0
Started task 2
Started task 4
Count: 1 increased couter 1
Count: 3 increased couter 2
Count: 0 increased couter 3
Count: 2 increased couter 4
Count: 4 increased couter 5
// Task dispatch executor
Started task 0
Started task 1
Started task 2
Started task 3
Started task 4
Count: 0 increased couter 1
Count: 1 increased couter 2
Count: 2 increased couter 3
Count: 3 increased couter 4
Count: 4 increased couter 5
But currently, TaskExecutor
is available in macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0. This makes it difficult for current code written with DispatchQueue
s to adopt concurrency incrementally without introducing change in the behaviour. Is there any chance TaskExecutor
can be back-ported till macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0?