Dart newbie here, I'm currently learning about asynchronous execution in Dart. I'm a bit irritated about how concurrency works in Dart, take the following scenario from their codelab:
void printOrderMessage () async {
try {
var order = await fetchUserOrder();
print('Awaiting user order...');
print(order);
} catch (err) {
print('Caught error: $err');
}
}
Future<String> fetchUserOrder() {
// Imagine that this function is more complex.
var str = Future.delayed(Duration(seconds: 4), () => throw 'Cannot locate user order');
return str;
}
Future<void> main() async {
await printOrderMessage();
}
In this case, the asynchronous operation is fetching the the user order from say a DB. Now because of Dart's await / async mechanism, every function that is related to the async operation is required to have a Future<> return type and must be tagged with async.
This feels clunky ... Imagine if some value deep in my function chain is being calculated asynchronously, would I really need to always return a future? Is there some other construct to synchronize code in Dart than await? Or have I misunderstood the concept?
If callers need to be able to wait for your asynchronous operation to finish, then your asynchronous function must return a Future that can be awaited. This is contagious; if callers of those callers need to be able to wait, then they too need to have Futures to wait upon.
If callers should not wait, then you can have a "fire-and-forget" function that does not need to return a Future:
Future<void> foo() {
// ...
}
// Does not need to return a Future. Consequently, callers cannot
// directly determine when `foo` completes.
void bar() {
foo();
}
Related
I have this function, want to call it synchronously, because if I call it async then I should use FutureBuilder, which I don't prefer because it has extra flicker if user scrolls too fast:
Future<String> getRealHTML(int chapter) async {
var key = _Html.keys.toList()[chapter];
var val = _Html.values.toList()[chapter];
if (val.Content.startsWith("filename:")) {
EpubContentFileRef value = contentRefHtml[key];
return await value.readContentAsText();
}
return null;
}
readContentAsText() returns a Future, you cannot call it synchronously since it is an I/O request which means it will take time to finish the request, that is why it is called asychronously.
I am building a screen in an app using an existing model class which I can't modify. The model has a method called refreshList() which fetches some data from an API, processes it, and finally it updates a List member in a model with the fetched data.
I want to know the state of the list - i.e. is it uninitialized, being fetched or already fetched - so that I can update the UI accordingly.
The problem is that refreshList() is not async, but uses an async method internally. Additionally, the internal method doesn't return a Future.
void refreshList(){
if( someCondition ){
_doRefreshList();
}
}
void _doRefreshList() async{
// ... do stuff
http.Response response = await http.get(url);
// ... do some more stuff
}
I would like to do something like
setState((){ _isLoading = true; });
await model.refreshList();
setState((){ _isLoading = false; });
but unfortunately I can't modify the methods in the model to return a Future.
Is there any way to achieve this without modifying refreshList() to be async and return a Future, and modifying _doRefreshList() to return a Future?
No, there is no way to wait for an asynchronous operation to complete unless it provides some way to get a callback when it's done.
The usual way to do that is to return a future. Having a callback function parameter is another approach, but it's rarely better than just returning a future.
In your example, refreshVerList has no way to report when it is done, and neither does _doRefreshList. You will have to add that somehow, and returning a future is the simplest and most consistent way to solve the problem for both functions.
(Technically, the _doRefreshList function does return a future, it's just typed as void. You can cast that future to Future, but I strongly discourage that kinds of shenanigans. If the function author decides to return something else, say null, at a later point, they are perfectly within their right to do since the return type is void.)
You can use Completer.
Completer<Null> onFetchCompleter;
Future<Null> _doRefreshList() async{
onFetchCompleter = Completer();
// ... do stuff
http.Response response = await http.get(url);
// ... do some more stuff
onFetchCompleter.complete();
return onFetchCompleter.future;
}
In the same way you can user a completer in your refreshVerList method.
Create a Future with the function reference as argument:
Future(model.refreshList);
Now you can use await since the above gives you a Future<void>:
setState(() { _isLoading = true; });
await Future(model.refreshList);
setState(() { _isLoading = false; });
Actually in Dart, in order to use await in function body, one need to declare whole function as async:
import "dart:async";
void main() async {
var x = await funcTwo();
print(x);
}
funcTwo() async {
return 42;
}
This code wouldn't work without marking main() as async
Error: Unexpected token 'await'.
But, doc says "The await expressions evaluates e, and then suspends the currently running function until the result is ready–that is, until the Future has completed" (Dart Language Asynchrony Support)
So, maybe I miss something, but there is no need to force function to be asynchronous? What is a rationale for making async declaration obligatory ?
In async functions await is rewritten to code where .then(...) is used instead of await.
The async modifier marks such a function as one that has to be rewritten and with that await is supported.
Without async you would have to write
void main() {
return funcTwo().then((x) {
print(x);
});
}
This is a very simple example but the rewriting can be rather complex when more of the async features are uses, like try/catch, await for(...), ...
One issue is that await was not originally part of the Dart language. To maintain backward compatibility with existing programs that could potentially use await as an identifier, the language designers added a mechanism to explicitly opt-in to using the new await keyword: by adding a (previously invalid) construct to declare a function async.
I want the method PrintOrderAsync to execute an external EXE to print an order.
The code for method PrintOrderAsync does not show any syntax errors
public async Task<string> PrintOrderAsync(string PrintExe, string ExePath, int nOrderNo)
{
await Task.Factory.StartNew(() => Process.Start(ExePath + PrintExe, nOrderNo.ToString()));
return "";
}
But I am struggling with the syntax for the calling method. Here is what I tried:
Task<string> result = PrintOrderAsync(PrintExe, ExePath, nOrderNo);
And I see syntax error on the above line. What am I missing?
Based on the code that you have shared here you are starting a process. This is concerning as you are not actually waiting for the result of the process, in fact -- it is fire and forget regardless of the async and await keywords unless you wait for the process to exit. There are several ways to do this:
public static Task WaitForExitAsync(this Process process,
int milliseconds,
CancellationToken cancellationToken = default(CancellationToken))
{
return Task.Run(() => process.WaitForExit(milliseconds), cancellationToken);
}
For example, here is an extension method you could use to wrap the waiting for the process in a Task. It could then be awaited like this:
public async Task PrintOrderAsync(string PrintExe, string ExePath, int nOrderNo)
{
return Process.Start(ExePath + PrintExe, nOrderNo.ToString())
.WaitForExitAsync(5000);
}
// Then you could await it wherever...
await PrintOrderAsync(PrintExe, ExePath, nOrderNo);
Alternatively, if you do not want to wait for it to complete (i.e.; you want fire and forget, like you have now) do this:
Process.Start(ExePath + PrintExe, nOrderNo.ToString())
Do not wrap it in a Task or anything, it is an entirely separate process anyways (personally, I prefer the first option I shared).
Try
string result = await PrintOrderAsync(PrintExe, ExePath, nOrderNo);
somehow my exceptions seem to being caught by method they are executing in. Here is the code to call the method. As you can see I create a cancellation token with a time out. I register a method to call when the cancellation token fires and then I start a new task. The cancellation token appears to be working OK. As does the registered method.
var cancellationToken = new CancellationTokenSource(subscriber.TimeToExpire).Token;
cancellationToken.Register(() =>
{
subscriber.Abort();
});
var task = Task<bool>.Factory.StartNew(() =>
{
subscriber.RunAsync((T)messagePacket.Body, cancellationToken);
return true;
})
.ContinueWith(anticedant =>
{
if (anticedant.IsCanceled)
{
Counter.Increment(12);
Trace.WriteLine("Request was canceled");
}
if (anticedant.IsFaulted)
{
Counter.Increment(13);
Trace.WriteLine("Request was canceled");
}
if (anticedant.IsCompleted)
{
Counter.Increment(14);
}
The next piece of code is the method that seems to be not only throwing the excetion (expected behavior. but also catching the exception.
public async override Task<bool> ProcessAsync(Message input, CancellationToken cancellationToken)
{
Random r = new Random();
Thread.Sleep(r.Next(90, 110));
cancellationToken.ThrowIfCancellationRequested();
return await DoSomethingAsync(input);
}
The exception is being thrown by the cancellation token but according to intellitrace it is being caught at the end of the method. I have tried a number of different options including throwing my own exception, but no matter what the continuewith function always executes the IsComleted or ran to completion code.
Any ideas on what I am doing wrong?
Thanks
I assume that RunAsync is the same as ProcessAsync.
The exception is being thrown by the cancellation token but according to intellitrace it is being caught at the end of the method.
Yup. Any async method will catch its own exceptions and place them on its returned Task. This is by design.
no matter what the continuewith function always executes the IsComleted or ran to completion code.
Well, let's take another look at the code:
var task = Task<bool>.Factory.StartNew(() =>
{
subscriber.RunAsync((T)messagePacket.Body, cancellationToken);
return true;
})
.ContinueWith(
Consider the lambda passed to StartNew: it calls RunAsync, it ignores the Task that it returns, and then it returns true (successfully). So the Task returned by StartNew will always return successfully. This is why the ContinueWith always executes for a successfully-completed task.
What you really want is to await the Task returned by RunAsync. So, something like this:
var task = Task.Run(async () =>
{
await subscriber.RunAsync((T)messagePacket.Body, cancellationToken);
return true;
})
.ContinueWith(
You're still ignoring the return value of RunAsync (the bool it returns is ignored), but you're not ignoring the Task itself (including cancellation/exception information).
P.S. I'm assuming there's a lot of code you're not showing us; using StartNew/Run to just kick off some async work and return a value is very expensive.
P.P.S. Use await Task.Delay instead of Thread.Sleep.