Why not just use a serial OperationQueue for each individual task? In such a case, override Operation with something that contains a CheckedContinuation.
My own version of a task queue, similar to yours but a little shorter and in my opinion easier to understand. I didn't include any check for cancellation as I think this should be left to the tasks themselves, just like OperationQueue does.
actor TaskQueue {
private let maxConcurrentTasks: Int
private var runningTasks = 0
private var queue = [CheckedContinuation<Void, Never>]()
init(maxConcurrentTasks: Int) {
self.maxConcurrentTasks = maxConcurrentTasks
}
func add<T>(_ task: @escaping () async throws -> T) async throws -> T {
if runningTasks >= maxConcurrentTasks {
await withCheckedContinuation { continuation in
queue.append(continuation)
}
}
runningTasks += 1
defer {
runningTasks -= 1
if runningTasks < maxConcurrentTasks && !queue.isEmpty {
queue.removeFirst().resume()
}
}
return try await task()
}
}