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.
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
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()
}
}
}
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.
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()
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 :
Using Dispatch Group & Semaphore to Group iOS Async Tasks
A Quick Look at Semaphores in Swift
Dexecutor for the rescue here
Disclaimer: I am the owner of Dexecutor
Dexecutor can be used easily for workflow like use case
Here is sample Application
Related
I have an async function and I want to execute some task after the function has returned to the original context.
Here's a sample:
actor MyActor {
func doTask() await -> Int {
let result = await operation()
Task {
print("2. All tasks done!")
}
return result
}
}
let actor = MyActor()
let taskResult = await actor.doTask()
print("1. Task done")
I want output always to be
Task done
All tasks done!
However, sometimes the output is reversed.
I suppose this is because Swift might be switching threads when returning to the original caller.
Is there any way to actually wait for it so that "2. All tasks done" is called last?
I want to execute some task after the function has returned to the original context.
There's no magic here. If you want a task to execute at a certain point, then you need to start it at that point, not before.
What you are asking for is almost literally the same as
How do I make the print("2 ...") appear after the print("1 ...") in the following.
struct MyStruct {
func doTask() -> Int {
let result = operation()
print("2. All tasks done!")
return result
}
}
let actor = MyStruct()
let taskResult = actor.doTask()
print("1. Task done")
In your case, the only way to ensure the print("2 ...") appears after the print("1 ...") is to start the asynchronous task after you have done the print("1 ...").
You'll need to split the actor function in two.
actor MyActor {
func doOperation() async -> Int {
let result = await operation()
return result
}
func doTask(_ result: Int) async {
Task {
print("2. All tasks done!")
// Use result in some way if you need to
}
}
}
let actor = MyActor()
let taskResult = await actor.doOperation()
print("1. Task done")
await actor.doTask(taskResult)
I'm trying to perform some iterative work, and use Combine to publish the progress (0.0 - 100.0) using a CurrentValueSubject, which my ViewModel will then subscribe to
(Edit: the ViewModel controls a SwiftUI ProgressView, which is why receive(on: DispatchQueue.main) is used)
What I'm seeing is that the outputs are being published, but sink doesn't receive any of them until the publisher has completed.
Here's a simplified example:
// Class that performs iterative calculations and publish its progress
class JobWorker {
private var subject: CurrentValueSubject<Double, Never>
private var progress = 0.0
init() {
self.subject = CurrentValueSubject<Double, Never>(progress)
}
func getPublisher() {
return subject.eraseToAnyPublisher()
}
func doWork() {
let tasks = [1,2,3,4,5]
tasks.forEach { num in
// ... perform some calculations ...
self.incrementProgress(20.0)
}
}
func incrementProgress(_ by: Double) {
progress += by
if progress >= 100.0 {
print("PUBLISH completion")
subject.send(completion: .finished)
} else {
print("PUBLISH value \(progress)")
subject.send(progress)
}
}
}
// ViewModel that subscribes to JobWorker's publisher and updates the progress in the view
final class JobViewModel: ObservableObject {
#Published var progress: Double = 0.0
private var cancellables = Set<AnyCancellable>()
private var jobWorker: JobWorker
init() {
self.jobWorker = JobWorker()
}
func runJob() {
self.jobWorker
.getPublisher()
.receive(on: DispatchQueue.main)
.handleEvents(
receiveSubscription: { _ in
print("RECEIVE subscription")
},
receiveOutput: { value in
print("RECEIVE output \(value)")
},
receiveCompletion: { _ in
print("RECEIVE completion")
},
receiveCancel: {
print("RECEIVE cancel")
},
receiveRequest: { _ in
print("RECEIVE demand")
}
)
.sink { [weak self] (completion) in
guard let self = self else { return }
print("SINK completion")
} receiveValue: { [weak self] (value) in
guard let self = self else { return }
print("SINK output \(value)")
self.progress = value
}
.store(in: &cancellables)
print("*** DO WORK ***")
self.jobWorker.doWork()
}
}
Calling JobViewModel.runJob results in the following output:
RECEIVE subscription
RECEIVE demand
RECEIVE output 0.0
SINK output 0.0
*** DO WORK ***
PUBLISH value 20.0
PUBLISH value 40.0
PUBLISH value 60.0
PUBLISH value 80.0
PUBLISH value 100.0
PUBLISH completion
RECEIVE output 20.0
SINK output 20.0
RECEIVE output 40.0
SINK output 40.0
RECEIVE output 60.0
SINK output 60.0
RECEIVE output 80.0
SINK output 80.0
RECEIVE output 100.0
SINK output 100.0
RECEIVE completion
SINK completion
After the CurrentValueSubject is first initialized, all of the outputs are published before handleEvents or sink receives anything.
Instead, I would have expected the output to show PUBLISH output x, RECEIVE output x, SINK output x for each of the outputs, followed by the completion.
The problem is that you are running your worker on the same thread where you are collecting the results.
Because you are doing a receive(on:) on the main DispatchQueue each value that passes through receive(on:) is roughly equivalent to putting a new block on the main queue to be executed when the queue is free.
Your worker fires up, executing synchronously on the main queue. While it's running, the main queue is tied up and not available for other work.
As the worker does its thing, it is publishing results to the subject, and as part of the publisher pipeline receive(on:) queues up the delivery of each result to the main queue, waiting for that queue to be free. The critical point, however, is that the main queue won't be free to handle those blocks, and report results, until the worker is done because the worker itself is tying up the main queue.
So none of your results are reported until after all the work is one.
I suspect what you want to do is run your work in a different context, off the main thread, so that it can complete asynchronously and only report the results on the main thread.
Here's a playground, based on your code, that does that:
import UIKit
import Combine
import PlaygroundSupport
class JobWorker {
private var subject = CurrentValueSubject<Double, Never>(0)
var publisher: AnyPublisher<Double, Never> {
get { subject.eraseToAnyPublisher() }
}
func doWork() async {
do {
for subtask in 1...5 {
guard !Task.isCancelled else { break }
print("doing task \(subtask)")
self.incrementProgress(by: 20)
try await Task.sleep(nanoseconds: 1 * NSEC_PER_SEC)
}
} catch is CancellationError {
print("The Tasks were cancelled")
} catch {
print("An unexpected error occured")
}
}
private func incrementProgress(by: Double) {
subject.value = subject.value + by;
if subject.value >= 100 {
subject.send(completion: .finished)
}
}
}
let worker = JobWorker()
let subscription = worker.publisher
.print()
.receive(on: DispatchQueue.main)
.sink { _ in
print("done")
} receiveValue: { value in
print("New Value Received \(value)")
}
Task {
await worker.doWork()
}
PlaygroundPage.current.needsIndefiniteExecution = true
I made your doWork function an async function so I could execute it from an independent Task. I also added a delay because it makes the asynchronous nature of the code a bit easier to see.
In the "main thread, I create a JobWorker and subscribe to its publisher, but to do the work I create a task and run doWork in that separate task. Progress is reported in the main thread, but the work is being done (and completed) in a different execution context.
How could I make my code wait until the task in DispatchQueue finishes? Does it need any CompletionHandler or something?
func myFunction() {
var a: Int?
DispatchQueue.main.async {
var b: Int = 3
a = b
}
// wait until the task finishes, then print
print(a) // - this will contain nil, of course, because it
// will execute before the code above
}
I'm using Xcode 8.2 and writing in Swift 3.
If you need to hide the asynchronous nature of myFunction from the caller, use DispatchGroups to achieve this. Otherwise, use a completion block. Find samples for both below.
DispatchGroup Sample
You can either get notified when the group's enter() and leave() calls are balanced:
func myFunction() {
var a = 0
let group = DispatchGroup()
group.enter()
DispatchQueue.main.async {
a = 1
group.leave()
}
// does not wait. But the code in notify() is executed
// after enter() and leave() calls are balanced
group.notify(queue: .main) {
print(a)
}
}
or you can wait:
func myFunction() {
var a = 0
let group = DispatchGroup()
group.enter()
// avoid deadlocks by not using .main queue here
DispatchQueue.global(qos: .default).async {
a = 1
group.leave()
}
// wait ...
group.wait()
print(a) // you could also `return a` here
}
Note: group.wait() blocks the current queue (probably the main queue in your case), so you have to dispatch.async on another queue (like in the above sample code) to avoid a deadlock.
Completion Block Sample
func myFunction(completion: #escaping (Int)->()) {
var a = 0
DispatchQueue.main.async {
let b: Int = 1
a = b
completion(a) // call completion after you have the result
}
}
// on caller side:
myFunction { result in
print("result: \(result)")
}
In Swift 3, there is no need for completion handler when DispatchQueue finishes one task.
Furthermore you can achieve your goal in different ways
One way is this:
var a: Int?
let queue = DispatchQueue(label: "com.app.queue")
queue.sync {
for i in 0..<10 {
print("Ⓜ️" , i)
a = i
}
}
print("After Queue \(a)")
It will wait until the loop finishes but in this case your main thread will block.
You can also do the same thing like this:
let myGroup = DispatchGroup()
myGroup.enter()
//// Do your task
myGroup.leave() //// When your task completes
myGroup.notify(queue: DispatchQueue.main) {
////// do your remaining work
}
One last thing: If you want to use completionHandler when your task completes using DispatchQueue, you can use DispatchWorkItem.
Here is an example how to use DispatchWorkItem:
let workItem = DispatchWorkItem {
// Do something
}
let queue = DispatchQueue.global()
queue.async {
workItem.perform()
}
workItem.notify(queue: DispatchQueue.main) {
// Here you can notify you Main thread
}
Swift 5 version of the solution
func myCriticalFunction() {
var value1: String?
var value2: String?
let group = DispatchGroup()
group.enter()
//async operation 1
DispatchQueue.global(qos: .default).async {
// Network calls or some other async task
value1 = //out of async task
group.leave()
}
group.enter()
//async operation 2
DispatchQueue.global(qos: .default).async {
// Network calls or some other async task
value2 = //out of async task
group.leave()
}
group.wait()
print("Value1 \(value1) , Value2 \(value2)")
}
Use dispatch group
dispatchGroup.enter()
FirstOperation(completion: { _ in
dispatchGroup.leave()
})
dispatchGroup.enter()
SecondOperation(completion: { _ in
dispatchGroup.leave()
})
dispatchGroup.wait() // Waits here on this thread until the two operations complete executing.
In Swift 5.5+ you can take advantage of Swift Concurrency which allows to return a value from a closure dispatched to the main thread
func myFunction() async {
var a : Int?
a = await MainActor.run {
let b = 3
return b
}
print(a)
}
Task {
await myFunction()
}
Swift 4
You can use Async Function for these situations. When you use DispatchGroup(),Sometimes deadlock may be occures.
var a: Int?
#objc func myFunction(completion:#escaping (Bool) -> () ) {
DispatchQueue.main.async {
let b: Int = 3
a = b
completion(true)
}
}
override func viewDidLoad() {
super.viewDidLoad()
myFunction { (status) in
if status {
print(self.a!)
}
}
}
Somehow the dispatchGroup enter() and leave() commands above didn't work for my case.
Using sleep(5) in a while loop on the background thread worked for me though. Leaving here in case it helps someone else and it didn't interfere with my other threads.
Here is the deal. I'm attempting to walk the tree. But want to do so concurrently. So each time i walk onto a node i need to concurrently walk all of it's nodes and so on.
But. I do not want to wait for the whole DispatchGroup to finish to get results since it's like a worst case scenario in Big O.
Instead i want to cancel all the other DispatchWorkItems and leave group for them in the successing one. Tried to do so with counting the task which ended. Obviously i'm doing something wrong or misunderstand how to use this.
The code below was written just for purpose of example and to test the idea.
Consider the real world situation is that in the DispatchWorkItem you can call recursively another handle function of a current node of tree.
func handle(completion: #escaping (Int) -> Void) {
var result: Int = 0
var count = 7
let group = DispatchGroup()
let queue = DispatchQueue(label: "q", attributes: .concurrent)
var items = [DispatchWorkItem]()
let item1 = DispatchWorkItem(flags: .inheritQoS) {
for _ in 0...1000 { continue }
count -= 1
group.leave()
print("left 1")
}
let item2 = DispatchWorkItem(flags: .inheritQoS) {
for _ in 0...2000 { continue }
count -= 1
group.leave()
print("left 2")
}
let item3 = DispatchWorkItem(flags: .inheritQoS) {
for _ in 0...6000 { continue }
count -= 1
group.leave()
print("left 3")
}
let item4 = DispatchWorkItem(flags: .inheritQoS) {
for _ in 0...3000 { continue }
result = 42
items.forEach { $0.cancel() }
for _ in 0..<count {
group.leave()
}
print("ok; left 4")
}
let item5 = DispatchWorkItem(flags: .inheritQoS) {
for _ in 0...50000 { continue }
count -= 1
group.leave()
print("left 5")
}
let item6 = DispatchWorkItem(flags: .inheritQoS) {
for _ in 0...6000 { continue }
count -= 1
group.leave()
print("left 6")
}
let item7 = DispatchWorkItem(flags: .inheritQoS) {
for _ in 0...8000 { continue }
count -= 1
group.leave()
print("left 7")
}
items.append(item1)
items.append(item2)
items.append(item3)
items.append(item4)
items.append(item5)
items.append(item6)
items.append(item7)
for item in items {
group.enter()
queue.async(execute: item)
}
group.notify(queue: queue) {
return
}
}
test() {
handle { result in
print(result)
}
}
You can't be reading from and writing to your count variable from multiple threads at once. You need to put a mutex lock on the count. You have an unstable situation trying to access and/or change count from multiple threads. Also, you should design this to not need counting at all.
A couple of thoughts:
If you want to cancel a time consuming task, you need to periodically check isCancelled. See https://stackoverflow.com/a/38372384/1271826.
If you are going to update count or items from multiple threads, you have to synchronize that interaction (e.g. with a lock or dedicated serial queue). Int and Array are not thread-safe, so you'll have to manage that yourself.
Keeping track of count and using DispatchGroup and keeping track of your own collection of DispatchWorkItem references is going to take a little work. Operation queues get your out of all of that. That having been said, if maximum efficiency is required, then perhaps you want to stay within dispatch queues, but it's just a lot more work.
I am trying to achieve NSOperationQueue finishing all tasks operation in swift 3. I create a below demo code and it is working according to my expectation.
func downloadTopStroiesDetails(){
let operationQueue: OperationQueue = OperationQueue()
let operation1 = BlockOperation() {
print("BlockOperation1")
for id in 0...5{
operationQueue.addOperation(downloadArticle(index: id))
}
let operation2 = BlockOperation() {
print("BlockOperation2")
}
operationQueue.addOperation(operation2)
}
operationQueue.addOperation(operation1)
}
func downloadArticle(index:Int) -> Operation {
let operation: Operation = BlockOperation { () -> Void in
print(index)
}
return operation
}
downloadTopStroiesDetails() // start calling
Output :
BlockOperation1
0
1
2
3
4
5
BlockOperation2
But when I call a Web API with Alamofire in downloadArticle method output is different.
func downloadArticle(index:Int) -> Operation {
let operation = BlockOperation(block: {
RequestManager.networkManager.fetchFromNetworkwithID(articleid: index) { (response:Any ,sucess:Bool) in
if sucess{
print(index)
//let art = article.init(json:(response as? json)!)!
// self.saveDataIntoCoreData(data: art)
//self.all_TopArticle.append(art)
}
};
})
return operation
}
Now output :
BlockOperation1
BlockOperation2
0
1
2
3
4
5
What i am doing wrong here ?
Your downloadArticle method is creating a block operation that completes immediately because it in turn performs an asynchronous operation.
You need to prevent the block from reaching the end until the async fetch completes. Using a semaphore would be one solution.
func downloadArticle(index:Int) -> Operation {
let operation = BlockOperation(block: {
let semaphore = DispatchSemaphore(value: 0)
RequestManager.networkManager.fetchFromNetworkwithID(articleid: index) { (response:Any ,sucess:Bool) in
if sucess{
print(index)
//let art = article.init(json:(response as? json)!)!
// self.saveDataIntoCoreData(data: art)
//self.all_TopArticle.append(art)
}
semaphore.signal()
};
semaphore.wait()
})
return operation
}
The use of this semaphore ensure the operation doesn't actually complete until the network fetch is also complete.
You might also want to make your operation queue serial instead of concurrent to ensure you only allow one operation to run at a time. If this is what you want, then set the operation queue's maxConcurrentOperationCount to 1.