完成第一个操作后,具有自定义“maxConcurrentOperationCount”的操作队列不会拾取/执行队列中的所有操作

Kir*_* S. 0 concurrency swift operationqueue

我确信我的逻辑有问题,只是无法弄清楚它是什么。

有一个“Service”类,它有一个操作队列:

class Service {

    let queue: OperationQueue = {

        var queue = OperationQueue()
        queue.name = "my.operationQueue"
        queue.maxConcurrentOperationCount = 1
        return queue
    }()

    func add(operation: Operation) {
        queue.addOperation(operation)
    }
}
Run Code Online (Sandbox Code Playgroud)

该操作是异步的,因此它会覆盖状态和函数start

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
    }
}
Run Code Online (Sandbox Code Playgroud)

我正在向队列添加多个操作(用于concurrentPerform测试目的,实际上,它是不同的):

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))")
}
Run Code Online (Sandbox Code Playgroud)

我期待什么

  • 20 个操作被添加到队列中(因为let iterations = 20
  • 1 个操作立即开始运行,其他操作在队列中等待(因为queue.maxConcurrentOperationCount = 1
  • 一旦第一个操作完成,第二个操作就会开始,依此类推。
  • 打印队列内容的最后一个块不应包含任何项目,或者最多包含 1-2 个剩余项目。

实际发生了什么

操作似乎按预期添加到队列中。

我看到只有 1 个操作开始并完成,其余操作从未开始。最后一个块在添加所有操作后 40 秒打印队列内容(大约足够完成所有或几乎所有操作的时间),显示剩余操作仍在队列中,未运行。这是一个例子:

<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>
 ]
Run Code Online (Sandbox Code Playgroud)

那么我做错了什么?

笔记:

  • 这不是错误的问题print,因为在实际代码中我没有使用它
  • 此外,在实际代码中没有DispatchQueue.global().asyncAfter(deadline: .now() + 2)- 这只是为了模拟正在运行的异步操作。

更新:我将问题提炼为maxConcurrentOperationCount:如果我删除该行queue.maxConcurrentOperationCount = 1,队列将按预期工作。将其设置为任何其他值都会产生类似的问题。

还是不明白为什么会出错。

Rob*_*Rob 6

问题是这些方法不符合 KVC/KVO 标准。正如Operation 文档所说:

\n
\n

该类NSOperation的多个属性符合键值编码 (KVC) 和键值观察 (KVO) 标准。

\n

\xe2\x80\xa6

\n

如果您为上述任何属性提供自定义实现,您的实现必须保持 KVC 和 KVO 合规性。

\n
\n

对并发程度的约束(例如,maxConcurrentOperationCountaddDependency(_:))依赖于 KVO 来知道先前的操作何时完成。如果未能执行所需的 KVO 通知,队列将不知道后续操作何时可以继续。

\n

有关示例实现,请参阅尝试理解异步操作子类的后半部分。

\n
\n

FWIW,这是一个异步操作实现:

\n
public class AsynchronousOperation: Operation {\n\n    @Atomic @objc private dynamic var state: OperationState = .ready\n\n    // MARK: - Various `Operation` properties\n\n    open         override var isReady:        Bool { state == .ready && super.isReady }\n    public final override var isExecuting:    Bool { state == .executing }\n    public final override var isFinished:     Bool { state == .finished }\n    public final override var isAsynchronous: Bool { true }\n\n    // KVO for dependent properties\n\n    open override class func keyPathsForValuesAffectingValue(forKey key: String) -> Set<String> {\n        if [#keyPath(isReady), #keyPath(isFinished), #keyPath(isExecuting)].contains(key) {\n            return [#keyPath(state)]\n        }\n\n        return super.keyPathsForValuesAffectingValue(forKey: key)\n    }\n\n    // Start\n\n    public final override func start() {\n        if isCancelled {\n            state = .finished\n            return\n        }\n\n        state = .executing\n\n        main()\n    }\n\n    /// Subclasses must implement this to perform their work and they must not call `super`. The default implementation of this function throws an exception.\n\n    open override func main() {\n        fatalError("Subclasses must implement `main`.")\n    }\n\n    /// Call this function to finish an operation that is currently executing\n\n    public final func finish() {\n        if !isFinished { state = .finished }\n    }\n}\n\nprivate extension AsynchronousOperation {\n    /// State for this operation.\n\n    @objc enum OperationState: Int {\n        case ready\n        case executing\n        case finished\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

具有以下内容:

\n
@propertyWrapper\npublic class Atomic<T> {\n    private var _wrappedValue: T\n    private let lock = NSLock()\n\n    public var wrappedValue: T {\n        get { lock.withLock { _wrappedValue } }\n        set { lock.withLock { _wrappedValue = newValue } }\n    }\n\n    public init(wrappedValue: T) {\n        _wrappedValue = wrappedValue\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

通过上述内容,我将异步Operation代码抽象为可以子类化并继承异步行为的代码。例如,这是一个与您的示例执行相同操作的操作asyncAfter(但有一些额外的OSLog路标,以便我可以直观地看到仪器中的操作):

\n
import os.log\n\nprivate let log = OSSignposter(subsystem: "Op", category: .pointsOfInterest)\n\nclass MyOperation: AsynchronousOperation {\n    var value: Int\n\n    init(value: Int) {\n        self.value = value\n        super.init()\n    }\n\n    override func main() {\n        let id = log.makeSignpostID()\n        let name: StaticString = "Operation"\n        let state = log.beginInterval(name, id: id, "\\(value)")\n        \n        DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [self] in\n            finish()\n            log.endInterval(name, state, "\\(value)")\n        }\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

然后 ...

\n
let queue = OperationQueue()\nqueue.maxConcurrentOperationCount = 1\n\nfor i in 0..<5 {\n    queue.addOperation(MyOperation(value: i))\n}\n
Run Code Online (Sandbox Code Playgroud)\n

...产生如下操作的时间表:

\n

在此输入图像描述

\n