What's difference between Future.value
vs Future.microtask
Case1:
Future.microtask(() => 1).then(print);
Future.microtask(() => Future(() => 2)).then(print);
Future.value(3).then(print);
Future.value(Future(() => 4)).then(print);
Output for this is:
1
3
4
2
Case2: And when I swaps statements
Future.value(3).then(print);
Future.value(Future(() => 4)).then(print);
Future.microtask(() => 1).then(print);
Future.microtask(() => Future(() => 2)).then(print);
output is:
3
1
4
2
Questions:
- What's difference between
Future.value
vsFuture.microtask
? - Which of two has more priority? Whether
Future.value
completes first orFuture.microtask
? - Why the order of the final output (
4
and2
) remains unchanged?
Can someone explain this behavior considering event and microtask queue?
CodePudding user response:
Future.microtask
schedules a microtask to execute the argument function. It then completes the future with the result of that function call.
Future()
and Future.delayed
schedules a timer task, the former with Duration.zero
, to execute a function, and complete the future with the result of that function call.
Future.value
takes a value, not a function to call. If you do Future.value(computation())
, the computation is performed (or at least started, in case it's async) right now.
If you do Future.microtask(computation)
, the computation is started in a later microtask.
In each case, if the function returns a future or the value passed to Future.value
is a future, then you'll also have to wait for that future to complete, before the future returned by the Future
constructor is completed with the same result.
For the concrete example:
Future.value(3).then(print);
This creates a future completed with the value 3
.
However, since futures promise to not call a callback, like then(print)
, immediately when the then
is called, it schedules a microtask to actually call the print
callback at a later time. So, you get an extra delay there.
In more detail:
Future.microtask(() => 1).then(print);
// This `Future.microtask(...)` creates future, call it F1,
// and schedules a microtask M1 to call `() => 1` later.
// Then adds callback C1 (`then(print)`) to F1, but F1 isn't completed yet,
// so nothing further happens.
Future.microtask(() => Future(() => 2)).then(print);
// Creates future F2 (`Future.microtask(...)`),
// schedules microtask M2 to run `() => Future(() => 2)` later,
// then callback C2 (`.then(print)`) to F2.
Future.value(3).then(print);
// Creates future F3 with value 3. Adds C3 (`then(print)`) to F3.
// Since F3 is complete, it schedules M3 to invoke C3.
Future.value(Future(() => 4)).then(print);
// Creates future F4 (`Future(() => 4)`)
// which starts *timer* T1 with duration zero to run `() => 4`.
// Then creates future F5 (`Future.value(...)`) with "value" F4.
// Completing with a future adds a callback C4 to F4,
// to notify F5 when a result is ready.
// Then adds callback C5 (`then(print)`) to F5.
That's what happens immediately. Then the event/microtask loop takes over.
- Eventually M1 runs. This executes
() => 1
to the value 1. - Then F1 is completed with the value 1.
- Then F1 notifies all its existing callbacks, which invokes C1 with 1.
- Which prints "1".
- Then M2 runs. This evaluates
Future(() => 2)
. - That creates future F6 (
Future(...)
and a timer T2 with duration zero. - It then completes F2 with the future F6,
- which means adding a callback C6 to F6 to notify F2 of a result.
- Then M3 runs. This invokes C3 with the value 3.
- Which prints "3".
- Now all microtasks are done.
- Timer T1 runs which evaluates
() => 4
to 4. - F4 completes with the value 4.
- F4 calls its existing callbacks, C4 with 4.
- That completes F5 with the value 4,
- and calls its existing callback C5 with the value 4.
- Which prints "4".
- Timer T2 runs
() => 2
and completes F6 with the value 2. - This runs F6's existing callback C6 with the value 2.
- That callback completes F2 with the value 2,
- and it calls F2's existing callback C2 with the value 2
- Which prints "2".
So, three microtasks, two timers, and some future result propagation later, you get the result you see.
The second example can be done in the same way:
Future.value(3).then(print);
// Schedule a microtask to print 3.
Future.value(Future(() => 4)).then(print);
// Schedule a timer to (going through an extra future) print 4.
Future.microtask(() => 1).then(print);
// Schedule a microtask to compute and print 1.
Future.microtask(() => Future(() => 2)).then(print);
// Schedule a microtask to schedule a timer to eventually print 2.
The microtask-only ones, 3 and 1, should print first in order. Then it should print 4, and then 2, because the 2-timer is scheduled after the 4-timer. 3-1-4-2, which is what you see.