Home > Mobile >  OperationQueue with custom `maxConcurrentOperationCount` does not pick up / execute all operations i
OperationQueue with custom `maxConcurrentOperationCount` does not pick up / execute all operations i

Time:03-25

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 enter image description here

  • Related