I'm sure there's something wrong with my logic, just cant figure out what it is.
There's a "Service" class, which has an operation queue:
class Service {
let queue: OperationQueue = {
var queue = OperationQueue()
queue.name = "my.operationQueue"
queue.maxConcurrentOperationCount = 1
return queue
}()
func add(operation: Operation) {
queue.addOperation(operation)
}
}
The operation is asynchronous, so it overrides the states, and the start
function:
class MyOp: Operation {
private var state: State = .ready
private var id: Int
init(id: Int) {
self.id = id
}
override var isAsynchronous: Bool {
return true
}
override var isReady: Bool {
return state == .ready
}
override var isExecuting: Bool {
return state == .started
}
/// See: `Operation`
override var isFinished: Bool {
return state == .finished || state == .cancelled
}
/// See: `Operation`
override var isCancelled: Bool {
return state == .cancelled
}
override func start() {
guard state == .ready else {
return
}
state = .started
print("\(Date()) started \(id)")
DispatchQueue.global().asyncAfter(deadline: .now() 2) {
self.state = .finished
print("\(Date()) finished \(self.id)")
}
}
}
private extension MyOp {
enum State {
case ready
case started
case cancelled
case finished
}
}
I am adding multiple operations to the queue (using concurrentPerform
for testing purposes, in reality, it's different):
let iterations = 20
let service = Service()
DispatchQueue.concurrentPerform(iterations: iterations) { iteration in
let operation = MyOp(id: iteration)
service.add(operation: operation)
}
DispatchQueue.global().asyncAfter(deadline: .now() 40) {
print("\(Date()) after run \(String(describing: service.queue.operations))")
}
What do I expect
- 20 operations are added to the queue (because
let iterations = 20
) - 1 operation starts to run immediately, others wait in the queue (because
queue.maxConcurrentOperationCount = 1
) - once the first operation completes, second starts, and so on.
- the final block, which prints the queue contents, should not contain any items, or at max 1-2 remaining items.
What actually happens
Operations are added to the queue as expected it seems.
I see that only 1 operation starts and finishes, the remaining operations never start. The final block, which prints queue contents 40 sec after all operations were added (roughly enough time to complete all, or almost all operations), shows that the remaining operations are still in the queue, not running. Here's an example:
<NSOperationQueue: 0x7fd477f09460>{name = 'my.operationQueue'}
2022-03-23 21:05:51 0000 started 11
2022-03-23 21:05:53 0000 finished 11
2022-03-23 21:06:31 0000 after run [
<__lldb_expr_25.MyOp 0x7fd479406660 isFinished=YES isReady=NO isCancelled=NO isExecuting=NO>,
<__lldb_expr_25.MyOp 0x7fd477f04080 isFinished=NO isReady=YES isCancelled=NO isExecuting=NO>,
<__lldb_expr_25.MyOp 0x7fd479206a70 isFinished=NO isReady=YES isCancelled=NO isExecuting=NO>,
<__lldb_expr_25.MyOp 0x7fd460904190 isFinished=NO isReady=YES isCancelled=NO isExecuting=NO>,
<__lldb_expr_25.MyOp 0x7fd479004080 isFinished=NO isReady=YES isCancelled=NO isExecuting=NO>,
<__lldb_expr_25.MyOp 0x7fd479406550 isFinished=NO isReady=YES isCancelled=NO isExecuting=NO>,
<__lldb_expr_25.MyOp 0x7fd460804080 isFinished=NO isReady=YES isCancelled=NO isExecuting=NO>,
<__lldb_expr_25.MyOp 0x7fd470904480 isFinished=NO isReady=YES isCancelled=NO isExecuting=NO>,
<__lldb_expr_25.MyOp 0x7fd460904080 isFinished=NO isReady=YES isCancelled=NO isExecuting=NO>,
<__lldb_expr_25.MyOp 0x7fd460804190 isFinished=NO isReady=YES isCancelled=NO isExecuting=NO>,
<__lldb_expr_25.MyOp 0x7fd460a04080 isFinished=NO isReady=YES isCancelled=NO isExecuting=NO>,
<__lldb_expr_25.MyOp 0x7fd4793068c0 isFinished=NO isReady=YES isCancelled=NO isExecuting=NO>,
<__lldb_expr_25.MyOp 0x7fd460b04080 isFinished=NO isReady=YES isCancelled=NO isExecuting=NO>,
<__lldb_expr_25.MyOp 0x7fd477f0a160 isFinished=NO isReady=YES isCancelled=NO isExecuting=NO>,
<__lldb_expr_25.MyOp 0x7fd460a04190 isFinished=NO isReady=YES isCancelled=NO isExecuting=NO>,
<__lldb_expr_25.MyOp 0x7fd479406770 isFinished=NO isReady=YES isCancelled=NO isExecuting=NO>,
<__lldb_expr_25.MyOp 0x7fd4608042a0 isFinished=NO isReady=YES isCancelled=NO isExecuting=NO>,
<__lldb_expr_25.MyOp 0x7fd4792092f0 isFinished=NO isReady=YES isCancelled=NO isExecuting=NO>,
<__lldb_expr_25.MyOp 0x7fd47910a360 isFinished=NO isReady=YES isCancelled=NO isExecuting=NO>,
<__lldb_expr_25.MyOp 0x7fd4609042a0 isFinished=NO isReady=YES isCancelled=NO isExecuting=NO>
]
So what am I doing wrong?
Note:
- This is not a problem with the
print
being wrong, as in actual code I am not using it - Also in actual code there's no
DispatchQueue.global().asyncAfter(deadline: .now() 2)
- this is just to simulate a running asynchronous operation.
Update: I distilled the problem to maxConcurrentOperationCount
: if I remove the line queue.maxConcurrentOperationCount = 1
, the queue is working as expected. Setting it to any other value, creates a similar problem.
Still don't understand why is it wrong.
CodePudding user response:
The issue is that the methods are not KVC/KVO compliant. As the Operation