How to code a slow background task made of of several slow steps using async / await - swift

I have a background task that has several slow steps to be processed in sequence. I am trying to understand any difference between the following two approaches in Swift.
First approach:
import SwiftUI
struct ContentView: View {
var body: some View {
Button("Start Slow Task") {
Task.detached(priority: .background) {
await slowBackgroundTask()
}
}
}
}
func slowBackgroundTask() async {
slowStep1()
slowStep2()
slowStep3()
}
func slowStep1() {
// takes a long time
}
func slowStep2() {
// takes a long time
}
func slowStep3() {
// takes a long time
}
Approach 2 is the same ContentView, but with the functions changed as follows.
func slowBackgroundTask() async {
await slowStep1()
await slowStep2()
await slowStep3()
}
func slowStep1() async {
// takes a long time
}
func slowStep2() async {
// takes a long time
}
func slowStep3() async {
// takes a long time
}
Is there any difference between these two patterns? I would be most grateful to understand this better.
Both versions build and run. I am trying to understand the difference.

Regarding the difference between these two patterns, they both will run the three slow steps sequentially. There are some very modest differences between the two, but likely nothing observable. I’d lean towards the first snippet, as there are fewer switches between tasks, but it almost certainly doesn’t matter too much.
FWIW, one would generally only mark a function as async if it is actually is asynchronous, i.e., if it has an await suspension point somewhere inside it. As The Swift Programming Language says,
An asynchronous function or asynchronous method is a special kind of function or method that can be suspended while it’s partway through execution.
But merely adding async qualifiers to synchronous functions has no material impact on their underlying synchronous behavior.
Now, if you are looking for a real performance improvement, the question is whether you might benefit from parallel execution (which neither of the snippets in the question can achieve). It depends upon a number of considerations:
Are the subtasks actually independent of each other? Or is subtask 1 dependent upon the results of subtask 2? Etc.
Are these subtasks trying to interact some some shared resource? Or is there going to be resource contention as you synchronize interaction with that shared resource?
Is there actually enough work being done in each subtask to justify the (admittedly very modest) overhead of parallel execution? If the subtasks are not sufficiently computationally intensive, introducing parallelism can actually make it slower.
But, if answering the above questions, you conclude that you do want to attempt parallelism, that begs the question as to how you might do that. You could use async let. Or you could use a “task group”. Or you could just make three independent detached tasks. There are a number of ways of tackling it. We would probably want to know more about these three subtasks to advise you further.
As a final, preemptive, observation as you consider parallel execution, please note that the iOS simulators suffer from artificially constrained “cooperative thread pools”. E.g., see Maximum number of threads with async-await task groups. In short, when testing parallel execution in Swift concurrency, it is best to test on a physical device, not a simulator.

Related

Is it ok to use DispatchQueue inside Task?

I'm now converting some of my codes into concurrency codes with async-await and Task. One thing I wonder is it ok to use DispatchQueue inside Task instances like
Task {
await someHeavyStuff()
DispatchQueue.main.async {
someUIThreadStuff()
}
}
As I know Task and DispatchQueue has little different mechanism to handle asynchronous things, so I am worried that using both could mess up the threading system.
(I know that I can use MainActor.run {} in this case)
You get away with DispatchQueue.main.async { … }, but you really should just retire this pattern. But if you have a big complicated project that you are slowly transitioning to Swift concurrency, and do not have the time and clean this up quite yet, yes, you can get away with this GCD call for now.
But the right solution is to just mark someUIThreadStuff as #MainActor and retire the DispatchQueue.main.async { … }. It is such a trivial fix, as is MainActor.run { … }. Of everything you might be tackling in the transition to Swift concurrency, this is one of the easiest things to just do right, and get rid of GCD API.
Where you have to be especially careful as you transition to Swift concurrency is where you use locks and semaphores or where you block the current thread. Swift concurrency can not reason about these, and those can be sources of problems. But a lingering dispatch to the main queue is unlikely to cause problems, though you should certainly excise it at your earliest convenience. See Swift concurrency: Behind the scenes, particularly the discussions about the runtime contract to never prevent forward progress.
As I look at your code snippet, I would be more concerned about the Task { … } to start someHeavyStuff. The name “startHeavyStuff” suggests something that is computationally expensive, blocking the current thread. But Task { … } is for launching asynchronous tasks on the current actor, not for running “heavy” tasks on a background thread. Now, someHeavyStuff is getting it off the current actor somehow, then ignore this caveat. But be careful you do not assume that Task { … } will behave like DispatchQueue.global().async { … }, because it does not.
I would suggest watching WWDC 2021 Swift concurrency: Update a sample app. It walks through a very practical exercise of refactoring your legacy code.

How can I use sleep in while without freezing all app?

I have a while loop which looping until some expression get true for breaking the loop, inside this loop I got a sleep(1), everything works fine, the only problem is the app get frozen until end of the while loop which is logical, I am trying to find out a way that the app would not get frozen while "while" working! Maybe multithreading programming? is it possible?
var repeatLoop = true
var count: Int = 0
while repeatLoop {
print(count)
count += 1
sleep(1)
if count >= 10
{
repeatLoop = false
}
}
print("finished!")
PS: My Goal is finding the Answer for this question rather than changing the way of solving, I mean there is so many way to get the same result without sleep or while.
With this program you can't! You need to look for options in your programming framework which can do multi threading. Your sleep() function must allow other threads to run.
One nice solution is to invert the logic and use Timers. instead of call sleep to block the operation till the next interaction. Your timer will call your routine many times you want.
Look this: The ultimate guide to Timer
The main thread / queue is the interface thread / queue, so time-consuming activity there freezes the interface.
Maybe multithreading programming
Indeed. The answer to your actual question is: do the time-consuming activity on a background thread / queue.
DispatchQueue.global(qos: .background).async {
// your code here
}
Of course, this raises the spectre of concurrency-related issues involving multiple simultaneous execution of the same code, non-thread-safe conflicts between accesses to shared data, etc. But that is the price of doing time-consuming things, and you just have to learn to cope.
The app won't freeze if you don't run this code on the main/UI thread.
There are several ways to do this on iOS. Here's one quick way:
DispatchQueue.global(qos: .background).async
{
// Your code here.
}

Dart/Flutter: How to best process hardware events in an infinite loop

I have an infinite loop that runs in a an async function in a Flutter application (although the code is pure Dart, no Flutter API is used on it).
Basically, it processes some math stuff, but I want to be able to update the data to be processed with an event coming from hardware (in this specific case from the microphone).
In order to give the fast while(true) the opportunity to get events from the outside, I added a delay with a duration of zero.
doStuff() async {
while (!stopFlag) {
processMathStuff(dataModifiedByHardwareCallback);
// Without this, the loop can't be interrupted.
await Future.delayed(Duration.zero);
}
}
And although that seems to work in most platforms, calling Future.delayed like that makes this loop much slower than I would like. The question is if is it there a better (faster) way of doing this. This even looks kind of a hack to me. I tried calling Future.delayed only every a certain amount of iterations, but doing so even as often as every 10 iterations, the system loses events.
By the way, processMathStuff does not contain loops internally, so it is really O(1).
NOTE: If I move the code to an Isolate, I get a similar problem with the Isolate not listening to the sendPort stuff if the while(true) lacks that kind of "yield".

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 {
///
}

Meaning of async in DispatchQueue

I'm watching the concurrent programming talk from WWDC, found here and I'm a little confused about the meaning of async. In Javascript world, we use "async" to describe processes that happen "out of order" or practically, processes that are not guaranteed to return before the next line of code is executed. However, in this talk it seems as though what the presenter is demonstrating is in fact a series of tasks being executed one after the other (ie. synchronous behavior)
Then in this code example
let queue = DispatchQueue(label: "com.example.imagetransform")
queue.async {
let smallImage = image.resize(to: rect)
DispatchQueue.main.async {
imageView.image = smallImage
}
}
This DispatchQueue doesn't seem to behave like the FIFO data structure it's supposed to be on first glance. I see that they are passing in a closure into the queue.async method, but I'm not sure how to add more blocks, or tasks, for it to execute.
I'm guessing the async nature of this is similar to Javascript callbacks in the sense that all the variables being assigned in the closure are only scoped to that closure and we can only act on that data within the closure.
DispatchQueues are FIFO with regard to the start of tasks. If they are concurrent queues, or if you're submitting async tasks, then there's no FIFO guarantee on when they finish
Concurrent queues allow multiple tasks to run at the same time. Tasks are guaranteed to start in the order they were added. Tasks can finish in any order and you have no knowledge of the time it will take for the next task to start, nor the number of tasks that are running at any given time.
- https://www.raywenderlich.com/148513/grand-central-dispatch-tutorial-swift-3-part-1