This is the 20th day of my participation in the November Gwen Challenge. Check out the details: The last Gwen Challenge 2021

1. Asynchronous programming

1.1 the Future

Future class, which represents the result of an asynchronous operation of type T. If the asynchronous operation does not require the result, the type is Future. So, first of all, the Future is a generic class, and you can specify the type. If no type is specified, the Future will perform dynamic derived types. Asynchronous programming is often used in network requests, so explore asynchronous programming in Flutter.

Dart is single-threaded, so there’s no underlying locking or anything like that, but that doesn’t mean DART can’t be asynchronous, and asynchronous doesn’t mean multithreaded. The following code is blocked when it runs to do something else. Async doesn’t work because it needs to be used with a Future.

By wrapping time-consuming operations in a Future, you can see that doing other things is not blocked, so now the method is asynchronous even without async, because the Future is already asynchronous. Or if async doesn’t execute asynchronously, a Future will.

Print (‘ end data = $_data’); Put it outside of the Future and it will execute before the code inside the Future.

1.2 async and await

So how to wait for the Future to finish executing code? You need to use await. After an await is added, the following code will wait for the Future to finish executing. ‘await’ here needs to be used with async, and async is meaningless without ‘await’. Operations following await also need to be asynchronous. So, a Future is used to execute asynchronously, and async and await are used to execute a block of code in the Future synchronously.

1.3 the Future. Then ()

The return value of all Future apis is still a Future object, so it’s easy to make chain calls. You can use then to put in specific waiting tasks without blocking the execution of subsequent tasks. If then returns no value, then value is null.

So if you see a return in the Future then value is the value of return. At this point, the data returned in the Future is wrapped by the Future and given to the value in the THEN.

1.4 the Future catchError

Errors in asynchrony are handled using catchError. When an exception occurs in an asynchronous task, an error is reported if the following methods are called because the task threw an exception without handling it.

GetData () async {print(' start data = $_data'); Future Future = Future(() {for (int I = 0; i < 10000000; I++) {} throw Exception(' network Exception '); Return "false network data "; }); future.then((value) { print('value = $value'); }); Print (' Do something else '); }Copy the code

Add code under.then

Future.catcherror ((e){print(" catchError: $e"); });Copy the code

An error was reported after the run, but an exception was caught. Normally, if you handle an exception, you should not report an error.

If the catch and then positions are swapped, an error is reported

“Then”; “then”;

So what do we do here? This is usually a chain call.

GetData () async {print(' start data = $_data'); Future Future = Future(() {for (int I = 0; i < 10000000; I++) {} throw Exception(' network Exception '); Return "false network data "; }).then((value) { print('value = $value'); }). CatchError ((e) {print(" catchError: $e"); }); Print (' Do something else '); }Copy the code

Or add onError to.then to handle errors

future.then((value) { print('value = $value'); },onError: (error){print(" $error"); });Copy the code

OnError is a catchError in a.then, and catchError is a catchError in a chain call. If you catchError in a.then, then value is the value of the catchError. .then before catchError will not be executed.WhenComplete is added after CatchError.

GetData () async {print(' start data = $_data'); Future Future = Future(() {for (int I = 0; i < 10000000; I++) {} throw Exception(' network Exception '); Return "false network data "; }); future.catchError((e) { print('error = $e'); Return "error "; }).then((value) => print('$value')); Future. whenComplete(() => print(" done ")); Print (' Do something else '); }Copy the code

Add a catchError after whenComplete, or use a chain call.

future.catchError((e) { print('error = $e'); Return "error "; }). Then ((value) = > print (' $value). The whenComplete (() = > print (" finished "));Copy the code

As you can see from the above example, the chain call avoids most of the errors, so it is common to use the chain call and put the catchError into the last call, so that the previous.then will not be executed if an exception occurs. If the chain is too long, you can create methods to make the chain simpler.

GetData () async {print(' start data = $_data'); Future Future = Future(() {for (int I = 0; i < 10000000; I++) {} throw Exception(' network Exception '); Return "false network data "; }).then(thenFunc).whenComplete(completeFunc).catchError((e) { print('error = $e'); Return "error "; }); Print (' Do something else '); } FutureOr thenFunc(String value) { } FutureOr<void> completeFunc() { }Copy the code

1.5 multiple Future

Futures are placed in queues, so they are executed sequentially. What is the resulting output of the following code?

void main() {
  testFuture();
  print("A");
}

void testFuture() {
   Future((){
    sleep(Duration(seconds: 2));
    print("C");
  });

  print("B");
}
Copy the code

B->A->C;So what does the following code print when it runs?

void main() {
  testFuture();
  print("A");
}

void testFuture() async {
  await Future((){
    sleep(Duration(seconds: 2));
    print("C");
  }).then((value) =>   print("D"));

  print("B");
}
Copy the code

A->C->D->B, because B is blocked by C, A will be executed first, then D will be executed after C, and finally B will be executed.So what is the output of the following code?

void main() { testFuture(); print("A"); } void testFuture() async {Future((){return "task 1"; }). Then ((value) => print("$value end "); Future((){return "task 2"; }). Then ((value) => print("$value end "); Future((){return "task 3"; }). Then ((value) => print("$value end "); Future((){return "task 4"; }). Then ((value) => print("$value end "); Print (" Task added complete "); }Copy the code

If yes, the task is added. ->A-> Task 1 ends -> Task 2 ends -> Task 3 ends -> Task 4 ends.

So the tasks must be done sequentially? After adding sleep in task 2, execute again and find that the task order is still the same.This indicates that asynchronous tasks are added in the order in which they are executed.

1.6 the Future. Wait

Future.wait can be used when you need to wait for multiple futures to complete and collect their results. At this point, the. Then wait until all the tasks in the wait have completed, and the value returned can be retrieved from the array. In a WAIT, tasks are processed simultaneously, but in order of addition, as opposed to chain execution, where one task is executed before the next.

Future. Wait ([Future (() {return "task 1";}) and Future (() {return "task 2";}) and Future (() {return "task 3"; }) and Future (() {return "task 4";}),]), then ((value) = > print (value value [1] [0] + + value [2]));Copy the code

1.7 microtask

Dart Event Loop

In Dart, there are actually two types of queues:

  • The Event queue contains all external events: information transfer between I/O, Mouse Events, Drawing Events, timers, and ISOLators.
  • A microtask queue represents an asynchronous task that will be completed within a short period of time. It has the highest priority, higher than the Event queue, and can hog the event loop as long as there are tasks in the queue. The tasks added to the MicroTask Queue are primarily generated within Dart.

Because microTask Queue has a higher priority than Event Queue, if microTask Queue has too many microtasks, it can hog the current Event loop. This blocks external events such as touches and draws in the Event queue.

In each event loop, Dart checks the first MicroTask Queue to see if there are any executable tasks, and if not, processes the subsequent event queue.

The lower priority event queue is the one we use most for asynchronous tasks. Dart provides a layer of encapsulation for the task creation of the Event Queue, the Future we often use in Dart.

Normally, the execution of a Future asynchronous task is relatively simple:

When a Future is declared, Dart places the function execution body of the asynchronous task into the Event Queue and immediately returns, with subsequent code continuing to execute synchronously. After the synchronized code is executed, the Event queue will fetch events in the order in which they were added to the event queue (i.e., the declaration order), and then synchronously execute the function body of the Future and subsequent operations. As mentioned above, the microTask queue has a high priority, so using MicroTask can make the task execute first. The following code first executes codes 1, 2, A, and B.

Void testFuture3() {print(' external code 1'); The Future (() = > print (' A ')), then ((value) = > print (' end of A ')); Future (() = > print (' B ')), then ((value) = > print (end of the 'B')); Print (' external code 2'); }Copy the code

When the microcode is added, the microtask is executed before the asynchronous task.

Void testFuture3() {print(' external code 1'); The Future (() = > print (' A ')), then ((value) = > print (' end of A ')); Future (() = > print (' B ')), then ((value) = > print (end of the 'B')); ScheduleMicrotask (() {print(" A"); }); Print (' external code 2'); }Copy the code

What does the following print look like after executing the task? Here 5 must be executed first, then microtask 3, then asynchronous tasks are executed in the order they were added, so future1 prints 1 and 4 first, and future2 prints 2 last, so 5 — 3 — 1 — 4 — 2 is printed;

void testFuture4() { Future future1 = Future((){print('1'); }); Future future2 = Future((){print('2'); }); ScheduleMicrotask (() {print(" duleMicrotask "); }); future1.then((value) => print('4')); print('5'); }Copy the code

Run and verify that it is true.

So what’s the print order here? Here future3 is added to the queue first, so it still takes precedence over 1, 2, so 6 is printed first, so 5 — 3 — 6 — 1 — 4 — 2;

Print result:

So if so, what would be the print result? As shown in the picture, future3 will complete itself and then start the loop again, so 5,3 will be followed by 6,8, then 7, then 142.

void testFuture4() {
  Future future3 = Future(() => null);
  future3.then((value) {
    print('6');
    scheduleMicrotask(() {
      print("7");
    });
  }).then((value) =>  print("8"));

  Future future1 = Future(() {
    print('1');
  });

  future1.then((value) => print('4'));
  Future future2 = Future(() {
    print('2');
  });
  scheduleMicrotask(() {
    print("3");
  });

  print('5');
}
Copy the code

Print result:

So what happens when I take the dot then out? This is essentially putting the. Then task into the microtask, so the 8 will still take precedence. This is why then tasks in Future1 are executed before THOSE in Future2.

  Future future4 =  future3.then((value) {
    print('6');
    scheduleMicrotask(() {
      print("7");
    });
  });
  
  future4.then((value) => print("8"));
Copy the code

Print result:

2. To summarize

  • Asynchrony in Dart
    • Future object to perform asynchronous operations.
      • Create the Future object through the factory constructor.
      • A function of Dart
        • The code executing the function is put into the event queue to execute asynchronously.
    • Async and await. If the Future internal code wants to execute synchronously, use the await modifier. Functions decorated with async are executed asynchronously.
    • Future result processing
      • Future.then is used to register callbacks to be called when a Future completes
      • Future.catchError Registers a callback to catch Future’s error
        • The future.catchError callback only handles errors thrown by the original Future, not by the callback function
        • OnError can only handle errors for the current Future
      • Future.whenComplete is always called after the Future has completed, whether due to error or after normal execution.
  • Dart event loop
    • In Dart, there are actually two types of queues:
      • The Event queue contains all external events: information transfer between I/O, Mouse Events, Drawing Events, timers, and ISOLators.
      • A microtask queue represents an asynchronous task that will be completed within a short period of time. It has the highest priority, higher than the Event queue, and can hog the event loop as long as there are tasks in the queue. The tasks added to the MicroTask Queue are primarily generated within Dart.