I'm learning Flutter and recently found Microtasks. I've read up on it and I understand what Microtasks are and what the Microtask queue is, but am still a little confused why I'd ever use one.
I'd appreciate an real-world example or explanation of when/why to use a Microtask.
Thanks.
Here is a concrete example I found usefull to understand it. Some operation while the widget tree is building are forbidden. Most of the time it is used in initState
Consider you are using the provider package
initState() {
super.initState();
context.read<MyNotifier>().fetchSomething(); // -> not work
}
Actually fetchSomething() will call a http package to get data and some stuff. This is not allowed because the state update is synchronous and here there is a lap of time during the value (new/old) can change (here before and after the data loaded from http).
This could cause inconsistencies in your UI and is therefore not allowed.
A workaround is to use a microtask to achieve it. This will schedule it to happen on the next async task cycle (i.e. after build is complete here).
initState() {
super.initState();
Future.microtask(() =>
context.read<MyNotifier>().fetchSomething(someValue); --> this work
);
}
To sum up this example, Microtask is usually created if we need to complete a task later, but before returning control to the event loop.
Related
The title is one way of doing what I ultimately want; others may be acceptable.
So I have a class that already exists. It controls some other stuff, and has a start and stop method. We are now making the list of "other stuff" depend on some settings saved in shared preferences, but shared preferences requires an asynchronous SharedPreferences.getInstance() call. I don't want to have to propagate asynchronicity all the way up the call chain. How do I avoid that? (Alternative 1: is there a library that you can persist data with, without messing directly with asynchronicity?)
Part of the problem is that the start and stop methods can be called at any point after the constructor (and indeed, when I just did SharedPreferences.getInstance().then(INITIALIZE LATE FINAL FIELDS), without await, start got called before the initialization happened and the code threw an exception.
(Alternative 2: is it acceptable to save the result of a then, and later call then on it? Like, do this._future = SharedPreferences.getInstance().then((prefs) {return LIST_OF_STUFF;}), and in start do this._future.then((listOfStuff) {START LIST OF STUFF; return listOfStuff;}), and similarly in stop? Seems...suspicious, but possibly functional.) I'd be ok with making the start and stop methods asynchronous, I guess, as long as I don't have to actually handle that in the code that calls them, but that doesn't really solve the problem that I need the SharedPreferences.getInstance() to have completed so I can have the list of stuff, first.
My default mechanism for handling concurrency is Communicating Sequential Processes, so I took a leaf from that book and thought, "What if I started an isolate that accepts start/stop messages? It can await the SharedPreferences, then loop over incoming messages and start/stop the Stuff as requested. I wrote an isolate to do that...and apparently you can't pass a ReceivePort in to an isolate (at least not by declaring it in the outer scope and using the reference in the isolate - it gives me an error Invalid argument(s): Illegal argument in isolate message: (object is aReceivePort)). You only get to directly hand the isolate a SendPort, so IT can send THE CALLER messages. The idiom I see in most places is to have the isolate send back a new SendPort, whose ReceivePort the isolate keeps. However, this returns me to my original problem - creating the isolate (and receiving its SendPort) is asynchronous, so it may not be done by the time start or stop get called. Since you pretty clearly CAN send a ReceivePort between...wait, I forgot it's technically not sending a ReceivePort, just another SendPort. Can you really only send SendPorts, specifically? That's...really obnoxious, if so. It seems pointlessly pedantic and obstructive. ...Ok, yeah, ReceivePort is explicitly restricted from being sent. Argh. I even thought I had a workaround to the "can't pass via scope" problem: make a new ReceivePort in the calling code, use its SendPort to send start and stop messages, and jerryrig some kind of handover of the ReceivePort to the isolate once that comes online. But if ReceivePort is entirely forbidden from being sent to the isolate, I guess that's out. So...how do I do what I want to do??? How do I pull a setting from disk, potentially queuing up start and stop messages while I wait for the persistence library to become available, without exposing to the calling code that we're making asynchronous calls under the hood?? Am I gonna have to make some kind of awful kludge of dangerous asynchronously-modified flags? I'll be rather disappointed in Flutter if that's what it comes down to.
As seen in https://stackoverflow.com/a/49458289/5150593, there are essentially two ways to access BuildContext after initially rendering a widget in Flutter (i.e. in initState):
Future.delayed(Duration.zero, () {
// context can be used here...
});
and
SchedulerBinding.instance.addPostFrameCallback((_) {
// context can be used here...
});
What difference is there between the two approaches? Is there any advantage to using one approach over the other? Are there any hidden side effects that I'm not seeing?
From my own exploration, if I use both methods in the same widget's initState, the order in which the callbacks are executed is the same as the order in which the callbacks are registered. I looked at the Flutter source code for Future.delayed and SchedulerBinding.instance.addPostFrameCallback but I don't really understand what's going on.
The link in the comment posted by #NBM (https://oleksandrkirichenko.com/blog/delayed-code-execution-in-flutter) pretty much answers the question. I will provide a brief explanation here for posterity.
The Future.delayed(Duration.zero, () {}) approach is using the behavior of Dart's event queue to delay execution until the next event loop iteration, at which point it is safe to access context since the widget is guaranteed to be built.
The SchedulerBinding.instance.addPostFrameCallback((_) {}); approach is specific to Flutter and is tied to the widget's lifecycle. The end effect is still to wait until the next event loop iteration, but semantically this approach makes more sense since we are solving a Flutter problem using a Flutter mechanism.
In Flutter we use async await and Future, can someone explain that if we don't use another thread (which we can't in dart) and run the job on main UIThread only won't the app become choppy because even if we are waiting for the job to execute it will ultimately execute on UIThread only.
I somewhere read about isolates also. But cannot paint the exact picture. If someone can explain in detail.
I think you missed something when it comes to asynchronous methods in Dart.
Dart is a single-threaded programming language, Java or C# are multi-threaded programming languages, forget async as a new thread, it doesn't happen in Dart.
Dart is a single-threaded programming language. This means is that Dart can only run one instruction at a time, while Java could run multiple instructions concurrently.
As a rule, everything you do in Dart will start in UI-Thread. Whatever method you call in Dart, whether using sync, async, then, they will be running on UI-Thread, since Dart is a single thread.
In single-threaded languages like Javascript and Dart, an async method is NOT executed in parallel but following the regular sequence of events, handled by the Event Loop. There are some problems (I would give some approaches, as we will see below) if you run the following code in a multithreaded language where fetch will take some time to execute:
String user = new Database.fetch(David);
String personalData = new Database.fetch(user);
You will receive David's data in user, and after that, you will receive your data.
This will lock your UI, unlike languages like Java which have Threads, where you can perform this task in the background, on another thread, and the UI-Thread will run smoothly.
If you do this at Dart
String user = new Database.fetch(David);
String personalData = new Database.fetch(user);
user will be null in personalData, because the fetch event is a Future.
How to solve this in Dart?
String user = await Database.fetch(David);
String personalData = await Database.fetch(user);
For those who like a more functional paradigm (I don't like it) you can use then.
Database.fetch(David).then((user){
Database.fetch(user).then((personal){
String personalData = personal;
});
});
However, imagine that you have billions of data in that database, this heavy task will probably cause the animations on your screen to freeze, and you will see a jank in the user's UI, for that purpose isolates were invented.
Dart Isolates give you a way to perform real multi-threading in Dart. They have their own separate heaps(memory), and run the code in the background, just like the Threads of multi-threaded languages. I could explain how isolates work, but it would make this response very long, and the goal is just to differentiate asynchronous from multi-threaded methods.
A simple way to solve the problem above using isolates would be using compute.
Compute was created to facilitate the creation of isolates, you just pass the function and the data that this function will execute, and that's it!
Important to remember that compute is a Future, so you have to use await or then to get its result.
In our example, we could create a new thread and get its result when we finish by just calling compute like this:
String user = await compute(Database.fetch,David);
String personalData = await compute(Database.fetch,user);
Very simple, isn't it?
In summary:
Everything that waits some time to be completed, in Dart is called a "Future".
To wait for the result of a future to be assigned to a variable, use await or then.
The asynchronous methods (await and then) can be used to obtain a result from a Future, and are executed ON THE MAIN THREAD because Dart is single-thread.
If you want to run any function on a new thread, you can create an isolate. Dart offers an easy-to-use isolate wrapper called compute, where you only need to pass one method that will be processed and the data that will be processed, and it will return its result in the future.
NOTE: if you are going to use compute make sure you are using a static or top-level method (see that in the example I used Database.fetch it was no accident if you need to call Database().fetch or need to create an instance of it, means it is not a static method and will not work with isolates).
English is not my first language and I didn't want to write so much because of that, but I hope I helped differentiate between multi-threaded asynchronous programming from single-threaded asynchronous programming.
I have just started trying Flutter today. I am following the official tutorial: https://flutter.dev/docs/development/ui/interactive (this question involves Step #3)
I understand the idea of Stateful Widgets and the need for setState(), but I dont understand the point of its parameter (function). Why should we pass a function to it? Couldnt setState be parameterless and we then would simply put the state changing code (that we would put on that parameter function) right before calling setState?
So basically my question is: what is the difference between
A) setState(() { myCounter++; });
and
B) myCounter++; setState( () {} );
?
Obs: on the tutorial I linked above if I do it either way the result is (or seems to be) the same.
The callback function is there to ensure that the changes you made to the models are synchronous and you should only set the things that you UI needs.
This will ensure that the changes are set before the framework refresh the UI because you can't do async work on the callback and the result can be predictable.
If you have to do heavy computations you should do it before call setState.
In your example, it is the same because is just a simple variable increment, but in practice, if you have heavy operations or async operations and want running those. inside the callback can have a performance impact if allowed.
Check the oficinal documentation for more examples: https://api.flutter.dev/flutter/widgets/State/setState.html
I have the same doubt, I checked the code of setState and still could not find any special usage for method parameter. So I think the design of setState method parameter is not reasonable, let setState be a no parameter function is better for understanding:
myCounter++;
setState();
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".