WanderingScouse
WanderingScouse

Reputation: 291

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
}

Upvotes: 15

Views: 20150

Answers (8)

NSparesh
NSparesh

Reputation: 19

func downloadFiles(files: [String], fromDir path: String, toDir localPath: String = "QuotesData", directoryType: FileManager.SearchPathDirectory = .libraryDirectory, maxConcurrentDownloads: Int = 5, qos: QualityOfService = .userInitiated, completion: @escaping (() -> ())) {
        
        // Create an operation queue and set its QoS
        let operationQueue = OperationQueue()
        operationQueue.maxConcurrentOperationCount = maxConcurrentDownloads
        operationQueue.qualityOfService = qos  // Set the QoS for the entire queue
        
        // Track completed downloads to show progress if necessary
        let totalDownloads = files.count
        var completedDownloads = 0
        
        // Create and add an operation for each file download
        for filename in files {
            guard !filename.isEmpty else { continue }
            
            // Create a BlockOperation for each download task
            let operation = BlockOperation {
                // Replace "&" in the file path if necessary
                let params = ["FilePath": path.replacingOccurrences(of: "&", with: "Ã"), "FileName": filename]
                
                // Perform the file download
                NetworkManager.shared.downloadFile(params: params, to: localPath, directoryType: directoryType) {
                    // Track progress and update UI if needed
                    completedDownloads += 1
                    print("Completed downloading: \(filename), Progress: \(completedDownloads)/\(totalDownloads)")
                    if totalDownloads == completedDownloads {
                        completion()
                    }
                
                }
            }
            
            // Set the QoS for each individual operation (optional, if you need specific control)
            operation.qualityOfService = qos
            
            // Add the operation to the queue
            operationQueue.addOperation(operation)
        }
    }

Upvotes: -1

MichaelMao
MichaelMao

Reputation: 571

I have met this problem, too. Well, instead of NSOperation, the GCD has already provide api: dispatch_group, for example:

dispatch_group_t myGroup = dispatch_group_create();

// task1
dispatch_group_enter(myGroup);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // excute task1
    // ...

    // task1 completed,leave group
    dispatch_group_leave(myGroup);
});

// task2
dispatch_group_enter(myGroup);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // excute task2
    // ...

    // task2 completed,leave group
    dispatch_group_leave(myGroup);
});

dispatch_group_notify(myGroup, dispatch_get_main_queue(), ^{
    // call back on all tasks c finished
    NSLog(@"all tasks are finished");
});

// continue other tasks.

Upvotes: -1

vadian
vadian

Reputation: 285250

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

Since iOS13 and macOS15 operationCount is deprecated. The replacement is to observe progress.completedUnitCount.

Another modern way is to use the KVO publisher of Combine

var cancellable: AnyCancellable?

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

Upvotes: 8

Rob
Rob

Reputation: 438232

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

Upvotes: 53

Peacemoon
Peacemoon

Reputation: 3328

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

Upvotes: 0

Siempay
Siempay

Reputation: 1002

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

Upvotes: 0

dbolli
dbolli

Reputation: 9

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.

Upvotes: 0

kasyanov-ms
kasyanov-ms

Reputation: 429

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

Upvotes: 2

Related Questions