简体   繁体   中英

Wait for all Operations in queue to finish before performing task

I have an Operation subclass and Operation queue with maxConcurrentOperationCount = 1.

This performs my operations in a sequential order that i add them which is good but now i need to wait until all operations have finished before running another process.

i was trying to use notification group but as this is run in a for loop as soon as the operations have been added to the queue the notification group fires.. How do i wait for all operations to leave the queue before running another process?

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
}

You can use operation dependencies to initiate some operation upon the completion of a series of other operations:

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)`

Or, in iOS 13 and later, you can use barriers:

let queue = OperationQueue()

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

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

A suitable solution is KVO

First before the loop add the observer (assuming queue is the OperationQueue instance)

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

Then implement

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)
    }
}

Edit:

In Swift 4 it's much easier

Declare a property:

var observation : NSKeyValueObservation?

and create the observer

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
    }
}

I use the next solution:

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)
    }
}

Set the maximum number of concurrent operations to 1

operationQueue.maxConcurrentOperationCount = 1

then each operation will be executed in order (as if each was dependent on the previous one) and your completion operation will execute at the end.

Code at the end of the queue refer to this link

NSOperation and NSOperationQueue are great and useful Foundation framework tools for asynchronous tasks. One thing puzzled me though: How can I run code after all my queue operations finish? The simple answer is: use dependencies between operations in the queue (unique feature of NSOperation). It's just 5 lines of code solution.

NSOperation dependency trick with Swift it is just easy to implement as this:

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)
}}

My solution is similar to that of https://stackoverflow.com/a/42496559/452115 , but I don't add the completionOperation in the main OperationQueue but into the queue itself. This works for me:

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 🎉")

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM