【问题标题】:Wait for all Operations in queue to finish before performing task在执行任务之前等待队列中的所有操作完成
【发布时间】:2017-07-18 15:49:17
【问题描述】:

我有一个操作子类和操作队列,maxConcurrentOperationCount = 1。

这会按我添加它们的顺序执行我的操作,这很好,但现在我需要等到所有操作完成后再运行另一个进程。

我试图使用通知组,但是因为一旦将操作添加到队列中,通知组就会在 for 循环中运行。我如何在运行另一个操作之前等待所有操作离开队列过程?

for (index, _) in  self.packArray.enumerated() {

    myGroup.enter()
    let myArrayOperation = ArrayOperation(collection: self.outerCollectionView, id: self.packArray[index].id, count: index)
    myArrayOperation.name = self.packArray[index].id
    downloadQueue.addOperation(myArrayOperation)
    myGroup.leave()

}

myGroup.notify(queue: .main) {
 // do stuff here
}

【问题讨论】:

  • myGroup.leave()放入操作的完成块中。
  • 使用 waitUntilAllOperationsAreFinished developer.apple.com/reference/foundation/operationqueue/…
  • 请注意:您的队列保证按提交顺序执行操作仅当队列中的所有操作具有相同的相对 queuePriority 并按照它们的顺序准备就绪添加到串行队列。请参阅 OperationQueue 文档 here 中的“确定执行顺序”部分

标签: ios swift nsoperation nsoperationqueue


【解决方案1】:

一个合适的解决方案是KVO

首先在循环之前添加观察者(假设queueOperationQueue 实例)

queue.addObserver(self, forKeyPath:"operations", options:.new, context:nil)

然后实施

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    if object as? OperationQueue == queue && keyPath == "operations" {
        if queue.operations.isEmpty {
            // Do something here when your queue has completed
            self.queue.removeObserver(self, forKeyPath:"operations")
        }
    } else {
        super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
    }
}

编辑:

在 Swift 4 中,这要容易得多

声明一个属性:

var observation : NSKeyValueObservation?

并创建观察者

observation = queue.observe(\.operationCount, options: [.new]) { [unowned self] (queue, change) in
    if change.newValue! == 0 {
        // Do something here when your queue has completed
        self.observation = nil
    }
}

由于 iOS13 和 macOS15 operationCount 已被弃用。替换是观察progress.completedUnitCount

另一种现代方式是使用Combine的KVO发布者

var cancellable: AnyCancellable?

cancellable = queue.publisher(for: \.progress.completedUnitCount)
    .filter{$0 == queue.progress.totalUnitCount}
    .sink() { _ in 
       print("queue finished") 
       self.cancellable = nil           
    }

【讨论】:

  • operationCount 属性已被弃用。这是否意味着它不应该被使用?
【解决方案2】:

您可以在完成一系列其他操作后使用操作依赖项来启动某些操作:

let queue = OperationQueue()

let completionOperation = BlockOperation {
    // all done
}

for object in objects {
    let operation = ...
    completionOperation.addDependency(operation)
    queue.addOperation(operation)
}

OperationQueue.main.addOperation(completionOperation)  // or, if you don't need it on main queue, just `queue.addOperation(completionOperation)`

或者,在 iOS 13 及更高版本中,您可以使用屏障:

let queue = OperationQueue()

for object in objects {
    queue.addOperation(...)
}

queue.addBarrierBlock {
    DispatchQueue.main.async {
        // all done
    }
}

【讨论】:

  • 非常干净。谢谢
  • 嘿,Rob,我确实做到了这一点,但我的 completionOperation 仍然被过早解雇。我的操作是异步网络操作,因此只有在响应返回时才考虑操作finished。我读到我需要覆盖isAsynchronous 属性以使其成为异步操作,但我读到如果我将操作添加到队列中,该属性将被忽略。所以我很困惑该怎么做。您能否提供进一步的建议?
  • 如果循环中有一系列操作,我想等待所有主题
  • 回复isAsynchronous:确实isAsynchronous在添加到队列时会被忽略,但在添加操作时需要所有isExecutingisFinished和关联的KVO一些异步进程到队列。话虽如此,我认为当操作实际上是异步的时,将isAsynchronous 设置为true 是最佳实践,以(a)反映操作的实际情况,以及(b)确保它可以正常工作。在队列中使用或只是手动启动。
  • 关于这里的操作依赖技术,看起来mainOperationQueue上的一个类var?该行是否应该更改为OperationQueue.main.addOperation(completionOperation)?这是否具有相同的含义?
【解决方案3】:

我使用下一个解决方案:

private let queue = OperationQueue()

private func addOperations(_ operations: [Operation], completionHandler: @escaping () -> ()) {
    DispatchQueue.global().async { [unowned self] in
        self.queue.addOperations(operations, waitUntilFinished: true)
        DispatchQueue.main.async(execute: completionHandler)
    }
}

【讨论】:

    【解决方案4】:

    设置最大并发操作数为1

    operationQueue.maxConcurrentOperationCount = 1
    

    然后每个操作将按顺序执行(好像每个操作都依赖于前一个),并且您的完成操作将在最后执行。

    【讨论】:

    • 这里请注意:这仅在队列中的所有操作具有相同的相对queuePriority 并且按照它们添加到串行队列的顺序准备就绪时才成立。请参阅OperationQueue 文档here 中的“确定执行顺序”部分
    【解决方案5】:

    队列末尾的代码 参考this link

    NSOperation 和 NSOperationQueue 是用于异步任务的优秀且有用的 Foundation 框架工具。有一件事让我感到困惑:我如何在所有队列操作完成后运行代码?简单的答案是:使用队列中操作之间的依赖关系(NSOperation 的独特功能)。只需 5 行代码即可解决。

    NSOperation 依赖技巧 使用 Swift 很容易实现如下:

    extension Array where Element: NSOperation {
    /// Execute block after all operations from the array.
    func onFinish(block: () -> Void) {
        let doneOperation = NSBlockOperation(block: block)
        self.forEach { [unowned doneOperation] in doneOperation.addDependency($0) }
        NSOperationQueue().addOperation(doneOperation)
    }}
    

    【讨论】:

      【解决方案6】:

      我的解决方案类似于https://stackoverflow.com/a/42496559/452115 的解决方案,但我没有将completionOperation 添加到主OperationQueue 中,而是添加到队列本身中。这对我有用:

      var a = [Int](repeating: 0, count: 10)
      
      let queue = OperationQueue()
      
      let completionOperation = BlockOperation {
          print(a)
      }
      
      queue.maxConcurrentOperationCount = 2
      for i in 0...9 {
          let operation = BlockOperation {
              a[i] = 1
          }
          completionOperation.addDependency(operation)
          queue.addOperation(operation)
      }
      
      queue.addOperation(completionOperation)
      
      print("Done ?")
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-02-15
        • 1970-01-01
        • 2016-03-23
        • 2019-07-26
        • 1970-01-01
        • 2021-12-22
        相关资源
        最近更新 更多