I'm trying to understand proper execution order of async functions in Dart. Here is a code that puzzles me:
void main() async {
print(1);
f1();
print(3);
}
void f1() async {
print(2);
}
According to spec first main() will be executed then f1(). So I expect output:
1
3
2
However real output is:
1
2
3
Does it mean f1() is executed synchronously?
However if I add await Future.delayed(Duration.zero); to f1() before print the output is as I expect:
void main() async {
print(1);
f1();
print(3);
}
void f1() async {
await Future.delayed(Duration.zero);
print(2);
}
1
3
2
Can anyone explain that?
Referring to previous answer there are two quotes from documentation that explain this:
asynchronous function: An asynchronous function performs at least one asynchronous operation and can also perform synchronous operations.
An async function runs synchronously until the first await keyword.
Just adding the word async does not make a function asynchronous. If you mark a function that is fully synchronous async, it will execute as any other synchronous function/code would. When you add the Future.delayed, it makes the function actually asynchronous, which puts your print(2) in the event queue so that it's executed later than print(3).
Related
I'm trying to receive an async function return value inside a flatMap and wrap it under a Task to allow for async functionality but I'm having this error when I'm trying to access the Task value:
'async' property access in a function that does not support concurrency
How do I go about returning the value?
Playground
import UIKit
public func isEvenNumber(num:(Int)) async -> Result<Int, Error> {
if num%2 == 0 {
print("EVEN")
return .success(1)
}
print("ODD")
return .success(0)
}
func profileAsyncFunc() async -> Result<Bool, Error> {
return await isEvenNumber(num: 3)
.flatMap{ _ -> Result<Bool,Error> in
Task {
return await testAsyncFunc()
}.value
}
}
func testAsyncFunc() async -> Result<Bool, Error> {
let basicTask = Task { () -> Result<Bool, Error> in
.success(true)
}
return await basicTask.value
}
Task {
await profileAsyncFunc()
}
A few observations:
Swift concurrency simplifies this quite a bit. No flatMap is needed.
The isEvenNumber is not really asynchronous unless you await something. Adding async qualifier does not make it asynchronous. It only means that you could add some asynchronous code in the function, but only if you await something within the function.
In Swift concurrency, the Result type is no longer needed and quickly becomes syntactic noise. In an async function, you simply either return a value, or throw an error.
Let us assume that isEvenNumber is just a placeholder for something sufficiently complicated that you did need to make it asynchronous to avoid blocking the current actor. Furthermore, while you do not throw errors, let us assume that this is a proxy for some method that would.
If all of that were the case, you would need to make sure that you get it off the current actor with a detached task, and then it would be asynchronous, as you would await its value. And I am arbitrarily defining isEven to throw an error if the value is negative. (Clearly, this is not a reasonable reason to throw an error, but included it for illustrative purposes.)
Thus:
enum NumberError: Error {
case negative
}
// This is not sufficiently computationally intensive to warrant making it run
// asynchronously, but let us assume that it was. You would do something like:
func isEven(_ num: Int) async throws -> Bool {
if num < 0 { throw NumberError.negative }
return await Task.detached {
num.isMultiple(of: 2)
}.value
}
func profileAsyncFunc() async throws -> Bool {
try await isEven(3)
}
Then you could do:
Task {
let isEven = try await profileAsyncFunc()
print(isEven)
}
You said:
I'm trying to receive an async function return value inside a flatMap …
Why? You do not need flatMap in Swift concurrency, as you might use in Combine.
If you are simply trying to adopt async-await, you can just excise this from your code. If there is some other problem that you are trying to solve by introducing flatMap, please edit the question providing a more complete example, and explain the intended rationale for flatMap.
As it stands, the example is sufficiently oversimplified that it makes it hard for us to understand the problem you are really trying to solve.
Suppose we need to execute some code when a function finishes, no matter how.
Example:
void myFunc() async {
await myLock.acquire();
if(...) {
myLock.release();
return;
}
...
myLock.release();
}
Many languages have features that allow to achieve this in a more elegant way than just manually calling myLock.release() before every return statement (For example defer in Go). Is something like that also possible in Dart?
Dart does not have RAII. You instead would need to use try-finally.
(Dart did recently (in 2.17) add Finalizers, but those would fire when objects are garbage collected, which might happen at some non-deterministic time, if ever.)
And just for the record, an example of using try/finally:
void myFunc() async {
await myLock.acquire();
try {
if(...) {
return;
}
...
} finally {
myLock.release();
}
}
You'd want to start the try after allocating the resource, so that you don't try to release if allocation throws.
Why we can have void myFunc() async{} function? Why void returning is acceptable
Actually every async is a Future and I except the Future<void> returning value
For the same reason:
void log(String message) {
getLogger.then((logger) {
logger.write(message);
});
}
would be allowed. It does something asynchronous, but doesn't return a future.
Sometimes you just want to do something which involves waiting for an asynchronous function, while not actually needing, or wanting, anyone to wait for you.
I'd be happier if void foo() async { ... } didn't return a future at all, but as it is, a void function may return any value, it's the caller's responsibility not to use that value for anything. That's what a void return type means.
The biggest issue with a void return type on an async function is that it might hide an easily made mistake. If the author intended to return a Future<void>, and just forgot to write the Future, then it won't be caught locally. You won't notice it until someone tries to do an await on the returned value and gets a warning about using the value of a void expression. So, do be careful.
Suppose this code without a block
int calculateValue (int a, int b) {
return doSomeStuff(a,b);
}
now suppose the function depends on something that is asynchronous. So I declare the asynchronous part as a block like this
int calculateValue (int a, int b) {
int (^myBlock)(int a, int b) = ^int(int a, int b) {
// do some complex asynchronous stuff
return result;
};
// can I do this?
return myBlock;
}
Is there any need to put the calculation inside the block into a dispatch_sync on main thread or something?
No you can't do that. The point of starting an async function is so that the caller can continue on with other work (perhaps launch other async functions?).
Assuming you are using dispatch_async() - from the docs:
The dispatch_async() function returns immediately, without waiting for the block to be invoked:
If you want to be able to work with result of the block from the caller you can pass in a reference to the caller as an argument to your block. Then when your async task is complete, call one of the caller's methods to pass your "return value".
Consider code like this:
import 'dart:async';
foo() {
new Timer(onesec, bar);
}
bar() {
throw "from bar";
}
const onesec = const Duration(seconds:1);
main() {
runZoned(() {
new Timer(onesec, foo);
},
onError: (e, stackTrace) => print(stackTrace));
}
How can I tell that bar was "called" by foo in the stackTrace that I print out?
I'd like to see something like:
bar
...
foo
...
main
Have a look at the stack_trace package. It uses zones to keep track of asynchronous callbacks. Capturing stack traces for every asynchronous callback is expensive, but for debugging it is definitely worth it.
Example output from the package:
http://dartlang.org/foo/bar.dart 10:11 Foo.<fn>.bar
http://dartlang.org/foo/baz.dart Foo.<fn>.bar
===== asynchronous gap ===========================
http://dartlang.org/foo/bang.dart 10:11 Foo.<fn>.bar
http://dartlang.org/foo/quux.dart Foo.<fn>.bar
According to the doc, the easiest way to get these traces is to use Chain.capture.