6

Referring https://developer.apple.com/reference/foundation/operation, I am having Playground setup as -

class myOperation1 : Operation {
    override func main() {
        print("op1 working....")
    }
}

class myOperation2 : Operation {
    override func main() {
        print("op2 working....")
    }
}

let op1 = myOperation1()
let op2 = myOperation2()

op1.completionBlock = {
    print("op1 finished")
}

op2.completionBlock = {
    print("op2 finished")
}

op2.addDependency(op1)

let opsQue = OperationQueue()
opsQue.addOperation(op1)
opsQue.addOperation(op2)

And console log is -

op1 working....
op2 working....
op1 finished
op2 finished

Shouldn't we expect output as result of dependency? -

op1 working....
op1 finished
op2 working....
op2 finished

Same result with using - opsQue.addOperations([op1, op2], waitUntilFinished: true)

op1 working....
op2 working....
op1 finished
op2 finished
BaSha
  • 2,356
  • 3
  • 21
  • 38
  • 1
    To avoid code ordering, you can use `opsQue.addOperations([op1, op2], waitUntilFinished: true) ` – Willjay Feb 14 '17 at 10:07
  • and try out with dependency and without dependency – Willjay Feb 14 '17 at 10:08
  • @WeiJay: Maybe I should not observe this behavior in Playground, in actual project it should work as expected – BaSha Feb 14 '17 at 11:00
  • 1
    @BaSha no, checking it on playground should reflects the same output, if you want to re-test it, just tap on the blue arrow button (execute playground), that will rerun the code and you would see different result at each run :) – Ahmad F Feb 14 '17 at 11:07
  • @AhmadF: Exactly, with same dependency I see different results :) – BaSha Feb 14 '17 at 11:14
  • Completion blocks for Operations run asynchronously, usually on a random secondary thread. Thus, it's totally "random" where the output of `print` gets placed in the console relative to the output produced by `print` of the worker thread. Note that several `print` functions called from different threads _may_ get serialised. This means, all what you are experiencing is _expected_. – CouchDeveloper Jun 08 '19 at 18:58

8 Answers8

6

In fact, I can't determine what's exactly the mystery of why your code does not work as it should, but I figured out 3 workarounds to achieve what are you trying to:

If you are expecting that the output should always be:

op1 working....
op1 finished
op2 working....
op2 finished

then:

1- You might want to add the second operation to the queue in the completion block of the first one, as follows:

class myOperation1 : Operation {
    override func main() {
        print("op1 working....")
    }
}

class myOperation2 : Operation {
    override func main() {
        print("op2 working....")
    }
}

let opsQue = OperationQueue()

let op1 = myOperation1()

op1.completionBlock = {
    print("op1 finished")

    opsQue.addOperation(op2)
}

let op2 = myOperation2()

op2.completionBlock = {
    print("op2 finished")
}

opsQue.addOperation(op1)

2- Setting maxConcurrentOperationCount operation queue to 1, as follows:

class myOperation1 : Operation {
    override func main() {
        print("op1 working....")
    }
}

class myOperation2 : Operation {
    override func main() {
        print("op2 working....")
    }
}

let op1 = myOperation1()
let op2 = myOperation2()

op1.completionBlock = {
    print("op1 finished")
}

op2.completionBlock = {
    print("op2 finished")
}

op2.addDependency(op1)

let opsQue = OperationQueue()
// setting maxConcurrentOperationCount to 1
opsQue.maxConcurrentOperationCount = 1
opsQue.addOperation(op1)
opsQue.addOperation(op2)

3- Calling waitUntilAllOperationsAreFinished() after adding the first operation to the queue, as follows:

let opsQue = OperationQueue()
opsQue.addOperation(op1)
opsQue.waitUntilAllOperationsAreFinished()
opsQue.addOperation(op2)

btw, for a non-complex task, I prefer to use GCDs.

Hope this helped.

Ahmad F
  • 30,560
  • 17
  • 97
  • 143
  • So what if there are 3 operations, so i need to add opsQue.waitUntilAllOperationsAreFinished() after each time i addOperation – Mr. Bean Sep 21 '22 at 09:08
3

The completion block is called after the dependancy operation starts, but it doesn't mean that the first operation didn't end.

As quoted in @Xoronis's answer:

The exact execution context for your completion block is not guaranteed but is typically a secondary thread. Therefore, you should not use this block to do any work that requires a very specific execution context.

https://developer.apple.com/documentation/foundation/operation/1408085-completionblock

Take a look at this example:

class myOperation1 : Operation {
    override func main() {
        print("op1 working....")
        for i in 1...10 {
            print("\(i)")
        }
    }
}

class myOperation2 : Operation {
    override func main() {
        print("op2 working....")
    }
}

let op1 = myOperation1()
let op2 = myOperation2()

op1.completionBlock = {
    print("op1 completed")
}

op2.completionBlock = {
    print("op2 completed")
}

op2.addDependency(op1)

let opsQue = OperationQueue()
opsQue.addOperations([op1, op2], waitUntilFinished: true)

will result in

op1 working....
1
2
3
4
5
6
7
8
9
10
op2 working....
op1 completed
op2 completed

The first operation does end before starting its dependancy, but the completion block is called after the dependancy already started.

Aviv Ben Shabat
  • 1,073
  • 2
  • 13
  • 33
2

According to the documentation for the completion block (emphasis mine),

The exact execution context for your completion block is not guaranteed but is typically a secondary thread. Therefore, you should not use this block to do any work that requires a very specific execution context. https://developer.apple.com/documentation/foundation/operation/1408085-completionblock

So, in a more realistic sense of when you want to know exactly when an operation is finished, you'd do something more like this:

class myOperation1 : Operation {
    override func main() {
        print("op1 working....")
        //Do things
        print("op1 finished")
    }
}

class myOperation2 : Operation {
    override func main() {
        print("op2 working....")
        //Do things
        print("op2 finished")
    }
}
Jordan
  • 494
  • 4
  • 10
2

You need to set maxConcurrentOperationCount to 1 of the opeation queue than it will work as expected.

     let operationQueue = OperationQueue()
     operationqueue?.maxConcurrentOperationCount = 1

    let operation1 = Operation()
    let operation2 = Operation()

    operation1.completionBlock = {
        print("operation1 finished")
    }

    operation2.completionBlock = {
        print("operation2 finished")
    }

    operation2.addDependency(operation1)

    operationQueue.addOperation(operation1)
    operationQueue.addOperation(operation2)
vikram
  • 181
  • 2
  • 5
1

By specifying the dependency, it is guaranteed that op2 gets scheduled after op1 completes, but not necessarily that op2 gets scheduled after the completion handler of op1 has finished.

Tali
  • 861
  • 8
  • 17
  • I think statement contradicts - The receiver is not considered ready to execute until all of its dependent operations have finished executing (https://developer.apple.com/reference/foundation/operation/1412859-adddependency) – BaSha Feb 17 '17 at 09:25
  • @BaSha: what do you mean? Where do you see a contradiction? `addDependency` adds a dependency on the operation, not on the completion handler... – Tali Feb 27 '17 at 12:36
0

initializing the operationQueue suspended will give you what you want.

let queue = OperationQueue()
let downloadOp = Operation()
let resizeOp = Operation()
downloadOp.dependency(resizeOp)
queue.isSuspended = true
queue.addOperation(downloadOp)
queue.addOperation(resizeOp)
queue.isSuspended = false
Marlon Monroy
  • 144
  • 1
  • 4
0

I tested to use isSuspended. The main queue could be operated after the completion of each operation.

class OperationChain: Operation {
    var mainQ: OperationQueue?
    var text: String?
    
    init(with name: String, by mainqueue:OperationQueue){
        self.text = name
        self.mainQ = mainqueue
    }
    
    override func main() {
        self.mainQ!.isSuspended = true
        print(text!)
        sleep(5)
        self.mainQ!.isSuspended = false
    }
}

let oq = OperationQueue()
oq.maxConcurrentOperationCount = 1

let q1 = OperationChain(with: "Operation.main.q1", by: oq)
print("q1")

q1.completionBlock = {
    //sleep(5)
    q1.mainQ!.isSuspended = true
    var i = 0
    repeat {
        i = i + 1
    } while i < 100
    print("q1.completionBlock") 
    q1.mainQ!.isSuspended = false
}

oq.addOperations([q1], waitUntilFinished: true)
0

You can use adapter to make sure the order of the execution:

op1 working....
op1 finished
op2 working....
op2 finished

Here is the modified code:

import Foundation

class myOperation1 : Operation {
    override func main() {
        print("op1 working....")
    }
}

class myOperation2 : Operation {
    override func main() {
        print("op2 working....")
    }
}

let op1 = myOperation1()
let op2 = myOperation2()

op1.completionBlock = {
    print("op1 finished")
}

op2.completionBlock = {
    print("op2 finished")
}

let adapter = BlockOperation(block: {})

adapter.addDependency(op1)
op2.addDependency(adapter)

let opsQue = OperationQueue()
opsQue.addOperation(op1)
opsQue.addOperation(op2)
opsQue.addOperation(adapter)
MCAmri
  • 91
  • 1
  • 4