Synchronous function inside Task or outside - any difference? - swift

Will there be any difference in running a function that is not marked as async and running it inside a Task?
.onChange(of: callState.current) { state in
viewModel.changeNavigation(title: state.controllerTitle) // 1 - outside
Task {
viewModel.changeNavigation(title: state.controllerTitle) // 2 - inside
Task.detached(priority: .background) {
await viewModel.audit(state: state)
}
}
}
onChange is called from MainActor (SwiftUI View).
Both options are executed on the main thread.
Is there any difference in the performance exerted on the main thread and UI?

The difference is how the function runs relative to code around it. With the viewModel.changeNavigation code outside of the task, it will certainly run before any of the code that follows it.
With it inside the task you know that it will run at some point in the future, but you don't have any idea what other code might run before it.
If something else has been sent to the main queue it might execute first. If there are other tasks in the same execution context they could run before that call too.
The performance impact of creating the task and having it work through the scheduler is non-zero, but at user interface speeds probably won't be noticeable.
But you've definitely changed the timing of when the code will run and that may cause race conditions and the like if you're not careful.

Related

Mutating conditional in DispatchQueue.main.asyncAfter behavior

I have a question regarding the behaviour of the DispatchQueue, particularly how asyncAfter would behave if you'd use a conditional of some published var that might change within the completion handler.
Let's say when the DispatchQueue is called, viewModel.someBool = true, but sometime during these 3.5 seconds, a function, that takes quite some time, is called that sets viewModel.someBool to false. Will the DispatchQueue always wait until all previous code is done executing, or is there any scenario in which the completion handler can run "in between" some other block of codes execution? All code is being run on the main thread, but I am still uncertain if this could cause bugs or not.
DispatchQueue.main.asyncAfter(deadline: .now() + 3.5) {
if viewModel.someBool {
// do something
}
}
Will the DispatchQueue always wait until all previous code is done executing, or is there any scenario in which the completion handler can run "in between" some other block of codes execution?
Depends if it's a Sequential queue or Concurrent queue. Sequential queue will finish task1 before starting task2; concurrent may run them in parallel. main thread is sequential, so you are good there, but...
You said yourself "sometime during these 3.5 seconds a function is called that sets viewModel.someBool to false". What if that "some time" is 1 nanosecond after a delayed task was picked up and started running?.. So now your function that changes viewModel.someBool to false needs to wait for your delayed task to complete.
So either this should be OK for your code (which is preferable, since such a strong dependency on order, especially in UI, usually means some design issues), or you need to guarantee the order in your code

How to make a serial queue of tasks running in main thread with asynchronous callbacks?

In this case I have to consider the asynchronous callback as part of the task. I want only one task running at the same time.
Here is boilerplate:
func queueATask() {
DispatchQueue.main.async {
doSomeHeavyWorkPartA()
//tunnelProvider is an instance of NETunnelProviderManager
tunnelProvider.saveToPreferences {
continueHeavyWorkPartB()
}
}
}
So I can make multiple calls of this function.But still before running a new task,the previous one must be finished (including partA and PartB).
queueATask()
queueATask()
queueATask()
I was considering DispatchGroup and DispatchSemaphore, but it needs to block the main queue which is not viable? All the tasks must be running in the main queue. If there is no partB callback block it would be much easier, I think this is the hard part for this situation.
Kindly guide me how to solve this issue.
Thanks

Is code within DispatchQueue.global.async executed serially?

I have some code that looks like this:
DispatchQueue.global(qos: .userInitiated).async {
self.fetchProjects()
DispatchQueue.main.async {
self.constructMenu()
}
}
My question is are the blocks within the global block executed serially? When I add print statements, they always executed in the same order, but I'm not sure if I'm getting lucky as looking at the documentation, it says:
Tasks submitted to the returned queue are scheduled concurrently with respect to one another.
I wonder if anyone can shed any light on this?
EDIT:
Apologies, I don't think I made the question clear. I would like for the method constructMenu to only be called once fetchProjects has completed. From what I can tell (by logging print statements) this is the case.
But I'm not really sure why that's the case if what Apple's documentation above says (where each task is scheduled concurrently) is true.
Is code within an async block always executed serially, or is the fact that the code seems to execute serially a result of using DispatchQueue.main or is it just 'luck' and at some point constructMenu will actually return before fetchProjects?
I would like for the method constructMenu to only be called once fetchProjects has completed. From what I can tell (by logging print statements) this is the case.
Yes, this is the case.
But I'm not really sure why that's the case if what Apple's documentation above says (where each task is scheduled concurrently) is true.
Apple’s documentation is saying that two separate dispatches may run concurrently with respect to each other.
Consider:
DispatchQueue.global(qos: .userInitiated).async {
foo()
}
DispatchQueue.global(qos: .userInitiated).async {
bar()
}
In this case, foo and bar may end up running at the same time. This is what Apple means by “Tasks submitted to the returned queue are scheduled concurrently.”
But consider:
DispatchQueue.global(qos: .userInitiated).async {
foo()
bar()
}
In this case, bar will not run until we return from foo.
Is code within an async block always executed serially, or is the fact that the code seems to execute serially a result of using DispatchQueue.main or is it just ‘luck’ and at some point constructMenu will actually return before fetchProjects?
No luck involved. It will never reach the DispatchQueue.main.async line until you return from fetchProjects.
There is one fairly major caveat, though. This assumes that fetchProjects won’t return until the fetch is done. That means that fetchProjects better not be initiating any asynchronous processes of its own (i.e. no network requests). If it does, you probably want to supply it with a completion handler, and put the call to constructMenu in that completion handler.
Yes, blocks of code are submitted as blocks, and those blocks run sequentially. So for your example:
DispatchQueue.global(qos: .userInitiated).async {
self.fetchProjects()
DispatchQueue.main.async {
self.constructMenu()
}
}
fetchProjects() must complete before constructMenu is enqueued. There is no magic here. The block between {...} is submitted as a block. At some point in the future it will executed. But the pieces of the block will not be considered in any granular way. They will start at the top, fetchProjects, and then the next line of code will be executed, DispatchQueue.main.async, which accepts as a parameter another block. The complier doesn't know anything about these blocks. It just passes them to functions, and those functions put them on queues.
DispatchQueue.global is a concurrent queue that mean all tasks submitted run asynchronously at the same time , if you need it serially create a custom queue
let serial = DispatchQueue(label: "com.queueName")
serial.sync {
///
}

Why does the order of a dispatched print() change when run in a Playground?

In a Udacity's GCD course there was a small quiz:
let q = DispatchQueue.global(qos: .userInteractive)
q.async { () -> Void in
print("tic")
}
print("tac")
Which will be printed first?
The correct answer is: tac, then tic. Seems logical.
But, why is it so only when I create an Xcode project? In a playground it prints tic then tac. What am I missing?
In GCD
DispatchQueue.global(qos: .userInteractive).async{}
is below
DispatchQueue.main.async{}
Even thought it has quality of service (qos) as UI thread it does not means it is a main thread. So may be there is a difference in performance with play ground and ui project
please check apples documentation as well.
Apples documentation on GCD
key to your answer is in the question what really are you asking the "system" to do, an by system that is the whatever the code is running on the playground, your computer/phone or emulator. You are executing an asynchronous "block" of code - print("tic") with some priority ".userInteractive". If the system handles the asynchronous block first or continues with "normal" execution depends on priority and available resources. With asynchronous calls there is no real why to guarantee that it is executed before or after the code continues that is the nature of being asynchronous i.e execute the block as soon as the system allows it, all without blocking your current work. So the difference you are seeing in playground vs a project/emulator is that the project/phone/emulator must keep the UI/primary thread responsive so it continue with print("tac"), while the development playground favors the thread executing print("tic"). Bottom line is it has to deal with priority of execution and the available resources and how its implemented on the system you're running the code.

swift stop loop and continue it with a button

is it possible to stop an for in loop and continue it and the stopped position with a button.
i mean something like this:
for x in 0..<5 {
if x == 3 {
// show a button
// this button have to be pressed
// which do some tasks
// when all tasks are finished >
// continue this loop at the stopped point
}
}
is this possible? if yes, how?
Short answer: use a semaphore
Longer answer:
Your situation is an example of the more general case of how to pause some computation, saving its current state (local variables, call stack, etc.), and later to resume it from the same point, with all state restored.
Some languages/systems provide coroutines to support this, others the more esoteric call with current continuation, neither are available to you (currently) Swift...
What you do have is Grand Central Dispatch (GCD), provided in Swift through Dispatch, which provides support for executing concurrent asynchronous tasks and synchronisation between them. Other concurrency mechanisms such as pthread are also available, but GCD tends to be recommended.
Using GCD an outline of a solution is:
Execute you loop as an asynchronous concurrent task. It must not be executing on the main thread or you will deadlock...
When you wish to pause:
Create a semaphore
Start another async task to display the button, run the other jobs etc. This new task must signal the semaphore when it is finished. The new task may call the main thread to perform UI operations.
Your loop task waits on the semaphore, this will block the loop task until the button task signals it.
This may sound complicated but with Swift block syntax and Dispatch it is quite simple. However you do need to read up on GCD first!
Alternatively you can ask whether you can restructure your solution into multiple parts so saving/restoring the current state is not required. You might find designs such as continuation passing style useful, which again is quite easy using Swift's blocks.
HTH