Use only `await` instead `await MainActor.run { }` for #MainActor properties - swift

Let assume a ObservableObject as the following:
class NavigationModel: ObservableObject {
#MainActor #Published name: String = ""
}
When changing the published property within an async code block running not in the main queue, I am normally using the following syntax:
await MainActor.run {
name = "foobar"
}
However, I have realised that the following syntax can also be compiled without errors:
await name = "foobar"
I am wondering if this short path is valid and provides same results?

Yes, the two snippets will behave the same.
The await will cause the current asynchronous call to "hop" the main actor and execute there.
The compiler (and Swift runtime) know that name can only be accessed from the main thread/actor, so will require you to either:
Access the property from an asynchronous block with #MainActor context.
Hop to the main actor from the current thread to execute there. Then, resume execution of the current function afterwards (however, the function could resume on a different thread than it was originally executing on prior to the hop to the main actor).
Your first snippet is essentially doing both of these steps, introducing a #MainActor scope to run your code in, then you are hopping to the main actor to execute it before continuing with your function call.
The second snippet is just skipping the scope creation part, running the single line of code on the main actor, then hopping back right away.
If you're running multiple things on the main actor you will want to reduce the number of hops that are performed, because this will introduce a lot of overhead if you are hopping back-and-forth to and from the main actor.
For example:
#MainActor func someUIOperation() async { ... }
func expensiveLotsOfHopsToMainActor() async {
for _ in 0..<100 {
await someUIOperation()
}
}
func betterOnlyOneHopToMainActor() async {
await MainActor.run {
for _ in 0..<100 {
someUIOperation()
}
}
}
See examples and more in the original pitch for #MainActor.

Related

Bound variable in ViewModel is not updating the displayed value

I was trying to create an countdown timer in ViewModel but i didnt found any method to do that so i ve tried to do this with task delay and while loop but it ends after first task delay. Do u know any other way how to do that or how to repair that one.
public PageViewModel()
{
MethodName();
}
public async void MethodName()
{
CountSeconds = 10;
while (CountSeconds > 0)
{
await Task.Delay(1000);
CountSeconds--;
}
}
The reason why you can`t see others is related to context. You trying to run async code in non-async context.
To solve this problem you can do several ways, which way to choose is your choice and depends on your needs:
await MethodName();
async Task MethodName()
{
CountSeconds = 10;
while (CountSeconds > 0)
{
await Task.Delay(1000);
CountSeconds--;
}
}
Another way is to create various of tasks and execute them, here you can see methods, which can help you.
And as Rand Random metioned it's not about MAUI, it`s about understanding of async programming itself, so for you will be usefull read more about it.
You can use Dispatacher.StartTimer() (available in the DispatcherExtensions class) to create a function that will execute every x seconds/minutes/hours (depending of what you're setting) using the device's clock.
To access the Application's Dispatcher from any class, use the following line:
var dispatcher = Application.Current.Dispatcher;
Since there is no documentation available yet for MAUI, you can read the Device.StartTimer() documentation from Xamarin, which acts exactly the same.

What is the "current actor" in the Swift Concurrency model?

Reading the Concurrency section of the Swift Guide, I came across:
To create an unstructured task that runs on the current actor, call the Task.init(priority:operation:) initializer. To create an unstructured task that’s not part of the current actor, known more specifically as a detached task, call the Task.detached(priority:operation:) class method.
There is the phrase "current actor". This is also mentioned in the documentation of Task.init:
Runs the given nonthrowing operation asynchronously as part of a new top-level task on behalf of the current actor.
Before, I thought I had understood what "current actor" meant:
if the code runs on the main queue, then the current actor can be thought of as the MainActor, since its executor is the main queue.
if the code is marked with a global actor attribute, then its current actor is that global actor
if the code is in an actor and is not nonisolated, then its current actor is that actor.
#SomeOtherActor
func somethingElse() {
DispatchQueue.main.async {
// current actor is MainActor
}
}
#MainActor
class Foo: NSObject {
func foo() { /* current actor is MainActor */ }
}
actor Bar {
func foo() { /* current actor is Bar */ }
}
However, recently I realised that I forgot to consider a situation - what happens on a global queue (or any other random queue)?
#SomeOtherActor
func somethingElse() {
DispatchQueue.global().async {
// current actor is?
}
}
When I tried accessing one of the actor-isolated properties of SomeOtherActor in the closure, I get the message:
Property 'x' isolated to global actor 'SomeOtherActor' can not be mutated from a non-isolated context
Is that Swift's way of saying there is no current actor? If so, what will Task.init do? The documentation doesn't really say.
Ideally, is there a way to programmatically print the current actor?
I thought the SE proposals would explain what "current actor" meansNone of the SE proposals mention the word "current actor": SE-0306, SE-0313, SE-0316, SE-0327, SE-0344.

How to call async function asynchronously without awaiting for the result

Let's say I have the following functions.
func first() async {
print("first")
}
func second() {
print("second")
}
func main() {
Task {
await first()
}
second()
}
main()
Even though marking first function as async does not make any sense as there is no async work it does, but still it is possible...
I was expecting that even though the first function is being awaited, it will be called asynchronously.
But actually the output is
first
second
How would I call the fist function asynchronously mimicking the GCD's variant of:
DispatchQueue.current.async { first() }
second()
This behavior will change depending upon the context.
If you invoke this from a non-isolated context, then first and second will run on separate threads. In this scenario, the second task is not actually waiting for the first task, but rather there is a race as to which will finish first. This can be illustrated if you do something time-consuming in the first task and you will see the second task is not waiting at all.
This introduces a race between first and second and you have no assurances as which order they will run. (In my tests, it runs second before first most of the time, but it can still occasionally run first before second.)
However, if you invoke this from an actor-isolated context, then first will wait for second to yield before running.
So, the question is, do you really care which order these two tasks start? If so, you can eliminate the race by (obviously) putting the Task { await first() } after the call to second. Or do you simply want to ensure that second won’t wait for first to finish? In that case, this already is the behavior and no change to your code is required.
You asked:
What if await first() needs to be run on the same queue as second() but asynchronously. … I am just thinking [that if it runs on background thread that it] would mean crashes due to updates of UI not from the main thread.
You can mark the routine to update the UI with #MainActor, which will cause it to run on the main thread. But note, do not use this qualifier with the time-consuming task, itself (because you do not want to block the main thread), but rather decouple the time-consuming operation from the UI update, and just mark the latter as #MainActor.
E.g., here is an example that manually calculates π asynchronously, and updates the UI when it is done:
func startCalculation() {
Task {
let pi = await calculatePi()
updateWithResults(pi)
}
updateThatCalculationIsUnderway() // this really should go before the Task to eliminate any races, but just to illustrate that this second routine really does not wait
}
// deliberately inefficient calculation of pi
func calculatePi() async -> Double {
await Task.detached {
var value: Double = 0
var denominator: Double = 1
var sign: Double = 1
var increment: Double = 0
repeat {
increment = 4 / denominator
value += sign * 4 / denominator
denominator += 2
sign *= -1
} while increment > 0.000000001
return value
}.value
}
func updateThatCalculationIsUnderway() {
statusLabel.text = "Calculating π"
}
#MainActor
func updateWithResults(_ value: Double) {
statusLabel.text = "Done"
resultLabel.text = formatter.string(for: value)
}
Note: To ensure this slow synchronous calculation of calculatePi is not run on the current actor (presumably the main actor), we want an “unstructured task”. Specifically, we want a “detached task”, i.e., one that is not run on the current actor. As the Unstructured Concurrency section of The Swift Programming Language: Concurrency: Tasks and Task Groups says:
To create an unstructured task that runs on the current actor, call the Task.init(priority:operation:) initializer. To create an unstructured task that’s not part of the current actor, known more specifically as a detached task, call the Task.detached(priority:operation:) class method.

Mutex is not held in this async block

I'm doing the Advanced coroutines with Kotlin flow and LiveData code lab and encountered this function in CacheOnSuccess.kt.
There is a comment that says "// Note: mutex is not held in this async block". What does this mean exactly? Why wouldn't the mutex be held in the async block? And what is the significance of that?
suspend fun getOrAwait(): T {
return supervisorScope {
// This function is thread-safe _iff_ deferred is #Volatile and all reads and writes
// hold the mutex.
// only allow one coroutine to try running block at a time by using a coroutine-base
// Mutex
val currentDeferred = mutex.withLock {
deferred?.let { return#withLock it }
async {
// Note: mutex is not held in this async block
block()
}.also {
// Note: mutex is held here
deferred = it
}
}
// await the result, with our custom error handling
currentDeferred.safeAwait()
}
}
according to withLock implementation, mutex is held on the just stack-frame, which means, after withLock execution the mutex is released, but code inside async may not execute right in that frame (maybe in another thread according to current Dispatchers), so probably when the block of async get executed, withLock call could have already returned, as for the also call, it is marked as inline, so it is executed in the current frame, right before withLock returned
The mutex is held by at most one coroutine at any time. async launches a coroutine which doesn't attempt to acquire the mutex. The significance of that is the same as for any other mutex -- the code inside the async block isn't guarded by the mutex, so it must not touch the state that is required to be guarded by it.

PromiseKit, how to await a finalized promise?

coming from the JS world I'm having a bit of problem wrapping my head around promise kit flavor of promises, I need a bit of help with the following.
Assume I have a function that returns a promise, say an api call, on some super class I await for that promise, then do some other action (potentially another network call), on that parent call I also have a catch block in order to set some error flags for example, so in the end I have something close to this:
func apiCall() -> Promise<Void> {
return Promise { seal in
// some network code at some point:
seal.fulfill(())
}
}
// in another class/object
func doApiCall() -> ? { // catch forces to return PMKFinalizer
return apiCall()
.done {
// do something funky here
}
.catch {
print("Could not do first request"
}
}
now I'm trying to write some unit tests for this functionality, so the response is mocked and I know it will not fail, I just need to await so I can verify the internal state of my class:
// on my test file
doApiCall().done {
// test my code, but I get an error because I cannot pipe a promise that already has a `.catch`
}
How would one go about solving this problem? I could use finally to chain the PMKFinalizer but that feels wrong
Another tangential question would be, is it possible to re catch the error on a higher level, let's say a UI component so it can hold some temporary error state? as far as I see I did not see a way to achieve this.
Many thanks 🙏