Understanding sequence of operations with dependency in Swift
Asked Answered
P

8

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
Purine answered 14/2, 2017 at 9:50 Comment(6)
To avoid code ordering, you can use opsQue.addOperations([op1, op2], waitUntilFinished: true) Commander
and try out with dependency and without dependencyCommander
@WeiJay: Maybe I should not observe this behavior in Playground, in actual project it should work as expectedPurine
@Purine 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 :)Selfabuse
@AhmadF: Exactly, with same dependency I see different results :)Purine
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.Incipit
S
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.

Selfabuse answered 14/2, 2017 at 13:53 Comment(1)
So what if there are 3 operations, so i need to add opsQue.waitUntilAllOperationsAreFinished() after each time i addOperationLayette
A
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.

Archaeozoic answered 16/7, 2018 at 6:44 Comment(0)
G
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")
    }
}
Godevil answered 13/7, 2018 at 22:15 Comment(0)
R
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)
Ruthenium answered 17/10, 2018 at 10:20 Comment(0)
I
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.

Incidental answered 14/2, 2017 at 14:32 Comment(2)
I think statement contradicts - The receiver is not considered ready to execute until all of its dependent operations have finished executing (developer.apple.com/reference/foundation/operation/…)Purine
@BaSha: what do you mean? Where do you see a contradiction? addDependency adds a dependency on the operation, not on the completion handler...Incidental
F
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
Flattie answered 23/5, 2017 at 18:22 Comment(0)
C
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)
Compete answered 23/2, 2019 at 17:14 Comment(0)
S
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)
Siddon answered 5/6, 2019 at 15:19 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.