Home > Back-end >  Dart: difference between Future.value vs Future.microtask
Dart: difference between Future.value vs Future.microtask

Time:01-19

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:

  1. What's difference between Future.value vs Future.microtask?
  2. Which of two has more priority? Whether Future.value completes first or Future.microtask?
  3. Why the order of the final output (4 and 2) 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.

  • Related