I've always considered async/await more elegant/sexy over the Futures API, but now I'm faced with a situation where the Future API implementation is very short and concise and the async/await alternative seems verbose and ugly.
I marked my two question #1 and #2 in the comments:
class ItemsRepository
{
Future<dynamic> item_int2string;
ItemsRepository() {
// #1
item_int2string =
rootBundle.loadString('assets/data/item_int2string.json').then(jsonDecode);
}
Future<String> getItem(String id) async {
// #2
return await item_int2string[id];
}
}
#1: How do I use async/await here instead of Future.then()? What's the most elegant solution?
#2: Is this efficient if the method is called a lot? How much overhead does await add? Should I make the resolved future an instance variable, aka
completedFuture ??= await item_int2string;
return completedFuture[id];
1: How do I use async/await here instead of Future.then()? What's the most elegant solution?
async methods are contagious. That means your ItemsRepository method has to be async in order to use await inside. This also means you have to call it asynchronously from other places. See example:
Future<dynamic> ItemsRepository() async {
// #1
myString = await rootBundle.loadString('assets/data/item_int2string.json');
// do something with my string here, which is not in a Future anymore...
}
Note that using .then is absolutely the same as await in a async function. It is just syntactic sugar. Note that you would use .then differently than in your example though:
ItemsRepository() {
// #1
rootBundle.loadString('assets/data/item_int2string.json').then((String myString) {
// do something with myString here, which is not in a Future anymore...
});
}
And for #2 don't worry about a performance impact of async code. The code will be executed at the same speed as synchronous code, just later whenever the callback happens. The only reason async exists is for having an easy way of allowing code to continue running while the system waits for the return of the asynchronously called portion. For example not block the UI while waiting for the disk to load a file.
I recommend you read the basic docs about async in Dart.
then and await are different. await will stop the program there until the Future task is finished. However then will not block the program. The block within then will be executed when the Future task is finished afterwards.
If you want your program to wait for the Future task, then use await. If you want your program to continue running and the Future task do it things "in the background", then use then.
Related
I understand that Dart is single-threaded and that within an isolate a function call is popped from the event loop queue and executed. There seems to be two cases, async and sync.
a) Async: An asynchronous function will run without interruption until it gets to the await keyword. At this point, it may release control of the instruction pointer or continue its routine. (i.e. async functions can be but are not required to be interrupted on await)
b) Sync: All instructions from setup -> body -> and teardown are executed without interruption. If this is the case, I would say that synchronous functions are atomic.
I have an event listener that may have multiple calls in the event loop queue. I think I have two options.
Using the Synchronized package
a) Async version:
import 'package:synchronized/synchronized.dart';
final Lock _lock = Lock();
...
() async {
await _lock.synchronized(() async {
if (_condition) {
_signal.complete(status);
_condition = !_condition;
}
});
}
b) Sync version:
() {
if (_condition) {
_signal.complete(status);
_condition = !_condition;
}
}
From my understanding of the Dart concurrency model these are equivalent. I prefer b) because it is simple. However, this requires that there cannot be a race condition between two calls to my sync event handler. I have used concurrency in languages with GIL and MT but not with the event-loop paradigm.
a) Async: An asynchronous function will run without interruption until it gets to the await keyword. At this point, it may release control of the instruction pointer or continue its routine. (i.e. async functions can be but are not required to be interrupted on await)
await always yields. It's equivalent to setting up a Future.then() callback and returning to the Dart event loop.
For your simple example, there's no reason to use _lock.synchronized(). Synchronous code cannot be interrupted, and isolates (as their name imply) don't share memory. You would want some form of locking mechanism if your callback did asynchronous work and you needed to prevent concurrent asynchronous operations from being interleaved.
Is it possible to make future somehow like a re-runnable task? For example, if I have to made a network call using a future and it failed for authentication reason. I would like to re-run the network call future once auth succeeded. How can I do that?
My expected code would probably look similar to this
Future task = fetchData();
Future handleService(task) async {
try {
final data = await task;
return data;
} catch (ex) {
// requires authentication
if(ex.code == 202) {
bool authSuccess = await reAuth();
if (authSuccess) {
await task
}
}
}
}
Simple answer: you can't re-run a Future.
Future can be completed only once. Moreover, Future represents the result of an async computation. I think about it that way: you run a task that returns a token (Future). When the tasks comletes, it sets the value on the Future.
On top of that, Future can have its value set only once, it cannot be completed with 2 different values (even by the task whose result it represents) Once a value is set, it will always hold the same one, and not allow modification.
In your case, you need to call fetchData again.
If you have a function that may return multiple values, you can use a Stream, but this approach doesn't fit your problem.
What is the best approach to wrap java 7 futures inside a kotlin suspend function?
Is there a way to convert a method returning Java 7 futures into a suspending function?
The process is pretty straightforward for arbitrary callbacks or java 8 completablefutures, as illustrated for example here:
* https://github.com/Kotlin/kotlin-coroutines/blob/master/kotlin-coroutines-informal.md#suspending-functions
In these cases, there is a hook that is triggered when the future is done, so it can be used to resume the continuation as soon as the value of the future is ready (or an exception is triggered).
Java 7 futures however don't expose a method that is invoked when the computation is over.
Converting a Java 7 future to a Java 8 completable future is not an option in my codebase.
Of course, i can create a suspend function that calls future.get() but this would be blocking, which breaks the overall purpose of using coroutine suspension.
Another option would be to submit a runnable to a new thread executor, and inside the runnable call future.get() and invoke a callback. This wrapper will make the code looks like "non-blocking" from the consumer point of view, the coroutine can suspend, but under the hood we are still writing blocking code and we are creating a new thread just for the sake of blocking it
Java 7 future is blocking. It is not designed for asynchronous APIs and does not provide any way to install a callback that is invoked when the future is complete. It means that there is no direct way to use suspendCoroutine with it, because suspendCoroutine is designed for use with asynchronous callback-using APIs.
However, if your code is, in fact, running under JDK 8 or a newer version, there are high chances that the actual Future instance that you have in your code happens to implement CompletionStage interface at run-time. You can try to cast it to CompletionStage and use ready-to-use CompletionStage.await extension from kotlinx-coroutines-jdk8 module of kotlinx.coroutines library.
Of course Roman is right that a Java Future does not let you provide a callback for when the work is done.
However, it does give you a way to check if the work is done, and if it is, then calling .get() won't block.
Luckily for us, we also have a cheap way to divert a thread to quickly do a poll check via coroutines.
Let's write that polling logic and also vend it as an extension method:
suspend fun <T> Future<T>.wait(): T {
while(!isDone)
delay(1) // or whatever you want your polling frequency to be
return get()
}
Then to use:
fun someBlockingWork(): Future<String> { ... }
suspend fun useWork() {
val result = someBlockingWork().wait()
println("Result: $result")
}
So we have millisecond-response time to our Futures completing without using any extra threads.
And of course you'll want to add some upper bound to use as a timeout so you don't end up waiting forever. In that case, we can update the code just a little:
suspend fun <T> Future<T>.wait(timeoutMs: Int = 60000): T? {
val start = System.currentTimeMillis()
while (!isDone) {
if (System.currentTimeMillis() - start > timeoutMs)
return null
delay(1)
}
return get()
}
You should be now be able to do this by creating another coroutine in the same scope that cancels the Future when the coroutine is cancelled.
withContext(Dispatchers.IO) {
val future = getSomeFuture()
coroutineScope {
val cancelJob = launch {
suspendCancellableCoroutine<Unit> { cont ->
cont.invokeOnCancellation {
future.cancel(true)
}
}
}
future.get().also {
cancelJob.cancel()
}
}
}
I have two functions
callee() async {
// do something that takes some time
}
caller () async {
await callee();
}
In this scenario, caller() waits till callee() finishes. I don't want that. I want caller() to complete right after invoking callee(). callee() can complete whenever in the future, I don't care. I just want to start it just like a thread and then forget about it.
Is this possible?
When you call the callee function, it returns a Future. The await then waits for that future to complete. If you don't await the future, it will eventually complete anyway, but your caller function won't be blocked on waiting for that. So, you can just do:
caller() {
callee(); // Ignore returned Future (at your own peril).
}
If you do that, you should be aware of what happens if callee fails with an error. That would make the returned future complete with that error, and if you don't listen on the future, that error is considered "uncaught". Uncaught errors are handled by the current Zone and the default behavior is to act like a top-level uncaught error which may kill your isolate.
So, remember to handle the error.
If callee can't fail, great, you're done (unless it fails anyway, then you'll have fun debugging that).
Actually, because of the risk of just forgetting to await a future, the highly reocmmended unawaited_futures lint requires that you don't just ignore a returned future, and instead wants you to do unawaited(callee()); to signal that it's deliberate. (The unawaited function can be imported from package:meta and will be available from the dart:async library in SDK version 2.14).
The unawaited function doesn't handle errors though, so if you can have errors, you should do something more.
You can handle the error locally:
caller() {
callee().catchError((e, s) {
logErrorSomehow(e, s);
});
}
(Since null safety, this code only works if the callee() future has a nullable value type. From Dart 2.14, you'll be able to use callee().ignore() instead, until then you can do callee().then((_) => null, onError: (e, s) => logErrorSomehow(e, s)); instead.)
or you can install an error handling zone and run your code in that:
runZoned(() {
myProgram();
}, onError: logErrorSomehow);
See the runZoned function and it's onError parameter.
Sure, just omit await. This way callee() is called immediately and when an async operation is called the call will be scheduled in the event queue for later execution and caller() is continued immediately afterwards.
This isn't like a thread though. As mentioned processing is enqueued to the event queue which means it won't be executed until the current task and all previously enqueued tasks are completed.
If you want real parallel execution you need to utilize isolates.
See also
https://www.dartlang.org/articles/event-loop/
https://api.dartlang.org/stable/1.16.1/dart-isolate/dart-isolate-library.html
https://pub.dartlang.org/packages/isolate
I'm playing with a tiny web server and I'm implementing one version using the async package, and one synchronous version executing each request in a separate isolate. I would like to simply pipe a file stream to the HttpResponse, but I can't do that synchronously. And I can't find a way to wait for neither the Stream nor a Future synchronously. I'm now using a RandomAccessFile instead which works, but it becomes messier.
One solution would be to execute a periodical timer to check if the future is completed (by setting a boolean or similar), but that is most definitely not something I want to use.
Is there a way to wait synchronously for a Future and a Stream? If not, why?
For future visitors coming here simply wanting to perform some task after a Future or Stream completes, use await and await for inside an async method.
Future
final myInt = await getFutureInt();
Stream
int mySum = 0;
await for (int someInt in myIntStream) {
mySum += someInt;
}
Note
This may be technically different than performing a synchronous task, but it achieves the goal of completing one task before doing another one.
AFAIK there isn't a way to wait synchronously for a Future or a Stream. Why? Because these are asynchronous pretty much definitionally, and as you are discovering, the APIs are designed with asynchronous behavior in mind.
There are a couple of Future constructors, Future.value() and Future.sync(), that execute immediately, but I don't think these are probably what you have in mind.