2

Situation:

I have 2 tasks says T1 & T2 in async background mode. T2 depends on T1 and have successBlock which is executes after the completion of the both tasks T1 & T2.

Quick diagram is below for better understanding.

enter image description here

Edit:

To better understanding the tasks, you can assume T1 and T2 are the API calls which always be going to execute in async mode. I need some output data from T1 to hit T2 API. After the completion of the both tasks I need to update UI.


To accomplish this scenario, I have added my first async work in T1 and second work in T2 and dependency of T2 to T1 and successblock have dependency on both tasks.

Code Work

  1. My Tasks

    class TaskManager {
    
        static let shared = TaskManager()
    
        func task1Call(complete: @escaping ()->()) {
            DispatchQueue.global(qos: .background).async {
                for i in 0...10 {
                    print("~~> Task 1 Executing ..", i)
                    sleep(1)
                }
                complete()
            }
        }
    
        func task2Call(complete: @escaping ()->()) {
            DispatchQueue.global(qos: .background).async {
                for i in 0...10 {
                    print("==> Task 2 Executing ..", i)
                    sleep(1)
                }
                complete()
            }
        }
    }
    
  2. Execute Tasks

    class Execution {
    
        // Managing tasks with OperationQueue
        func executeTaskWithOperation()  {
    
            let t1 = BlockOperation {
                TaskManager.shared.task1Call {
                    print("Task 1 Completed")
                }
            }
    
            let t2 = BlockOperation {
                TaskManager.shared.task2Call {
                    print("Task 2 Completed")
                }
            }
    
            let successBlock = BlockOperation {
                print("Tasks Completed")
            }
    
            let oper = OperationQueue()
    
            t2.addDependency(t1)
            successBlock.addDependency(t2)
            successBlock.addDependency(t1)
    
            oper.addOperations([t1, t2, successBlock], waitUntilFinished: true)
    
        }
    }
    
    let e = Execution()
    e.executeTaskWithOperation()
    

Issue:

Both tasks are executing parallelly and successBlock executes before the completion of task 1 and task 2.

Console Output:

==> Task 2 Executing .. 0
Tasks Completed
~~> Task 1 Executing .. 0
~~> Task 1 Executing .. 1
==> Task 2 Executing .. 1
==> Task 2 Executing .. 2
~~> Task 1 Executing .. 2
==> Task 2 Executing .. 3
~~> Task 1 Executing .. 3
==> Task 2 Executing .. 4
~~> Task 1 Executing .. 4
==> Task 2 Executing .. 5
~~> Task 1 Executing .. 5
==> Task 2 Executing .. 6
~~> Task 1 Executing .. 6
==> Task 2 Executing .. 7
~~> Task 1 Executing .. 7
==> Task 2 Executing .. 8
~~> Task 1 Executing .. 8
==> Task 2 Executing .. 9
~~> Task 1 Executing .. 9
~~> Task 1 Executing .. 10
==> Task 2 Executing .. 10
Task 1 Completed
Task 2 Completed

I unable to figure out what wrong I am doing, even same code work fines when I use sync mode instead of async.

dahiya_boy
  • 9,298
  • 1
  • 30
  • 51
  • so you want it in a serial manner, why not use a dispatchGroup call instead? in your queue I didn't see any suspension on the running queue which doesn't prevent NSOperation to get the next process once the current one goes to a diff thread – Joshua Dec 09 '19 at 12:35
  • @Joshua , Sorry I didn't understand **in your queue I didn't see any suspension on the running queue which doesn't prevent NSOperation to get the next process once the current one goes to a diff thread** – dahiya_boy Dec 09 '19 at 12:40
  • 1
    oh, what I mean is that it didn't prevent NSOperation to perform the next process in your example [t1, t2] t1 executes and goes to background the moment it goes to background there's nothing informing NSOperation to wait for it to complete before proceeding to the next process (t2). Sync prevents that from happening by blocking the current thread until it finishes. if you want more control I suggest using a GroupDispatchQueue or even a DispatchSemaphore – Joshua Dec 09 '19 at 22:24
  • @Joshua Thanks buddy, it worked and I added conclude answer. If in case, you need to add any more points to my answer then please add it. – dahiya_boy Dec 10 '19 at 06:29
  • 1
    glad I managed to give you an idea. cheers – Joshua Dec 10 '19 at 10:08

3 Answers3

0

Your t1 and t2 are block operations that spawn background threads (which each do some printing and then exit, but it doesn't matter). Once they finish spawning, they're considered completed. successBlock depends on the two background threads being spawned, and then it's done. You want the work in the BlockOperation itself:

class Execution {

    // Managing tasks with OperationQueue
    func executeTaskWithOperation()  {

      let t1 = BlockOperation {
          for i in 0...10 {
              print("~~> Task 1 Executing ..", i)
              sleep(1)
          }
          print("Task 1 completed")
        }

        let t2 = BlockOperation {
          for i in 0...10 {
            print("==> Task 2 Executing ..", i)
            sleep(1)
          }
         print("Task 2 Completed")
        }

        let successBlock = BlockOperation {
            print("Tasks Completed")
        }

        let oper = OperationQueue()

        t2.addDependency(t1)  // Remove this to see concurrent exec of t1 and t2
        successBlock.addDependency(t2)
        successBlock.addDependency(t1)

        oper.addOperations([t1, t2, successBlock], waitUntilFinished: true)

    }
}

let e = Execution()
e.executeTaskWithOperation()

Edit: For execution on a background thread, override Operation.

class AsyncOp: Operation {
  let task: String
  var running = false
  var done = false

  init(_ task: String) {
    self.task = task
  }

  override var isAsynchronous: Bool { true }
  override var isExecuting: Bool {
    get { running }
    set {
      willChangeValue(forKey: "isExecuting")
      running = newValue
      didChangeValue(forKey: "isExecuting")
    }
  }
  override var isFinished: Bool {
    get { done }
    set {
      willChangeValue(forKey: "isFinished")
      done = newValue
      didChangeValue(forKey: "isFinished")
    }
  }

  override func main() {
    DispatchQueue.global(qos: .background).async {
      self.isExecuting = true
      for i in 0...10 {
        print("\(self.task) Executing ..", i)
        sleep(1)
      }
      print("Done")
      self.isExecuting = false
      self.isFinished = true
    }
  }

  override func start() {
    print("\(task) starting")
    main()
  }
}

class Execution {
  // Managing tasks with OperationQueue
  func executeTaskWithOperation()  {
    let t1 = AsyncOp("task1")
    let t2 = AsyncOp("task2")
    let successBlock = BlockOperation {
      print("Tasks Completed")
    }

    let oper = OperationQueue()
    t2.addDependency(t1)
    successBlock.addDependency(t2)
    successBlock.addDependency(t1)
    oper.addOperations([t1, t2, successBlock], waitUntilFinished: true)
  }
}

let e = Execution()
e.executeTaskWithOperation()
bg2b
  • 1,939
  • 2
  • 11
  • 18
  • Bro I already mentioned this point in the last line of question, check **... even same code work fines when I use sync mode instead of async.** – dahiya_boy Dec 09 '19 at 12:28
  • Remove the dependency and they will execute in parallel. The point is that spawning background threads makes t1 and t2 consider themselves done. All your original code is doing is making sure that the thread spawning happens in a particular order, and that the background threads spawn before the successBlock completes. – bg2b Dec 09 '19 at 12:32
  • Let me re-phase **I want to execute my tasks in background thread, T2 needs to be executed after T1 and success block needs to be called after the completion of T1 & T2**. In your case, I have to execute code parallelly and in the background thread, which is not in my requirement. – dahiya_boy Dec 09 '19 at 12:36
  • Then you want to override Operation and use isAsynchronous, not use BlockOperation. Give me a few minutes and I'll see if I can put something together to illustrate. – bg2b Dec 09 '19 at 12:40
0

After Joshua's comment , I able to conclude the answer.


Execution changed from OperationQueue to DispatchGroup and DispatchSemaphore.

DispatchGroup : It makes sure both task tasks are done and then it calls notify block.

DispatchSemaphore : It holds the async resource with wait command until we wont send the signal command i.e. we are saying to semaphore to hold yourself until the task1 is not completed.

Sample code of tasks.

class Execution {
    // Managing tasks with DispatchGroup

    func executeTaskWithGroup() {
        let groups = DispatchGroup()
        let semaphore = DispatchSemaphore(value: 1)
        groups.enter()
        semaphore.wait()
        TaskManager.shared.task1Call {
            groups.leave()
            semaphore.signal()
        }

        groups.enter()
        TaskManager.shared.task2Call {
            groups.leave()
        }

        groups.notify(queue: DispatchQueue.global(qos: .background)) {
            print("Tasks Completed")
        }

    }

}

To execute command all we need to do is.

let e = Execution()
e.executeTaskWithGroup()

But above code is executed in the main thread and block the UI. To prevent this you need to call above piece of code in background queue like below.

let queue = DispatchQueue.init(label: "MyQueue", qos: .background, attributes: .concurrent, autoreleaseFrequency: .workItem, target: nil)

queue.async {
    let e = Execution()
    e.executeTaskWithGroup()
}

Now everything works fine as per my needed.


AddOn

In case, if someone requirement is to call multiple API along with the above scenario then add your tasks in async in the queue.

let queue = DispatchQueue.init(label: "MyQueue", qos: .background, attributes: .concurrent, autoreleaseFrequency: .workItem, target: nil)

queue.async {
    let e1 = Execution()
    e1.executeTaskWithGroup()
}

queue.async {
    let e2 = Execution()
    e2.executeTaskWithGroup()
}

Now both e1 and e2 executes parallelly without blocking main thread.


References :

dahiya_boy
  • 9,298
  • 1
  • 30
  • 51
0

Dexecutor for the rescue here

Disclaimer: I am the owner of Dexecutor

Dexecutor can be used easily for workflow like use case

enter image description here

Here is sample Application

craftsmannadeem
  • 2,665
  • 26
  • 22