B. Use THE ISOLATE or Worker to run computer – intensive tasks in order to ensure that the App responds to user activities in a timely manner. The implementation of Isolate could be a separate thread, or a separate process, depending on how the Dart VM is implemented

Multicore cpus are used in most computers, even on mobile platforms. In order to take advantage of multi-core performance, developers generally use shared memory data to ensure the correct execution of multiple threads. However, sharing data with multiple threads often causes many potential problems and causes code to run incorrectly.

  • All Dart code runs in _ quarantined areas, not threads
  • Each quarantine has its own memory heap, ensuring that the state of each quarantine is not accessed by other quarantines

支那

Concurrent (Concurrency)

Concurrency is the Concurrency of executing multiple sequences of instructions. Dart is a single-threaded language, just like JS, so there is no multithreading in Dart. So what do we do if we have a multi-task parallel scenario? Dart provides a independently running worker that is ** similar to Java new threads but cannot share memory. It is part of a new and independent Dart execution environment, isolate. As a tool for working in parallel, AND as the name suggests, is a separate unit of running code. The only way to send data between them is to pass messages, just as messages are passed between clients and servers. The ISOLATE allows applications to take full advantage of multi-core microprocessors. For example, the default main method for executing tasks is a default ISOLATE. As you can see, if you want to perform multiple tasks in parallel within dart, Dart: THE ISOLATE ** package is the DART solution for capturing single-threaded DART code and allowing applications to use more of the hardware available so how do the ISOLates interact with each other? How did the isolate manage its own tasks?

Isolate.spawn

Spawn method source

spawn<T>( 
    void entryPoint(T message), 
    T message, { 
        bool paused: false.boolErrorsAreFatal, SendPort onExit, SendPort onError}) → Future<Isolate>// You can see that eventually a Isolate object is returned. What is the Future
Copy the code

Let’s take an example to better understand this concept

import 'dart:isolate';

void foo(var message) {
  print('execution from foo ... the message is :${message}');
}

void main() {
  Isolate.spawn(foo, 'Hello!! ');
  Isolate.spawn(foo, 'Greetings!! ');
  Isolate.spawn(foo, 'Welcome!! ');
  print('execution from main1');
  print('execution from main2');
  print('execution from main3');
}
Copy the code

Here, the spawn method of the Isolate class helps run function foo in parallel with the rest of our code. The spawn function takes two arguments

  • Generated functions, and
  • The object to be passed to the derived function
  • If no object is passed to the generated function, a NULL value can be passed

The two functions (foo and main) may not be run in the same order every time. There is no guarantee when foo is executed and when main() is executed. The output is different at each run time

Generally speaking, the isolates created using Spawn have a control port and a capability for controlled objects. Of course, we don’t have this capability. How do the isolates interact with each other? Let’s look at the flow chartIsolate interaction. PNG

From the figure above, we can see that the two ISOLATE send messages to each other using SendPort, and there is a corresponding ReceivePort in the ISOLATE to receive messages for processing, but we need to pay attention to the following: ReceivePort and SendPort have a pair on each ISOLATE, Only the ReceivePort in the same ISOLATE can receive the message sent by SendPort of the current class and process it. Spawn of the ISOLATE is used to create the spawn of the isolate with control capabilities. The second parameter is SendPort of the current ISOLATE to the newly created instance, so that the newly created instance can send messages to the original isolate to communicate with the original. Let’s look at an example of Isolate interaction:

import 'dart:isolate';

int i;

void main() {
  i = 10;
  SendPort childSendPort;
  print("The main function () outputs I = because it assigns I 10." + i.toString());
  Create a message sink - this is the default isolate of main, which we can call the main process
  // To create a new ISOLATE with a sender, the first parameter is the business logic function of the new ISOLATE with memory isolation. The second parameter is passed when the isolate is created. Normally, we pass the sender of the current ISOLATE
  ReceivePort receivePort = new ReceivePort();
  Isolate.spawn(isolateVice, receivePort.sendPort);

  // The main process receives messages from the ISOLATE that holds the main process sender
  receivePort.listen((message) {
    // The other ISOLATE can send sendPort to the main process. The main process can also send messages to the created ISOLATE to complete the interaction
    if (message is SendPort) {
      childSendPort = message;
      message.send("———————— received sender of function isolateVice() ————————");
    } else {
      print("Received message from function isolateVice() :" + message);
      if(childSendPort ! =null) {
        childSendPort.send('What do you like about me, I can't change it! '); // Make a reply}}}); }/// The specific business logic functions of the new ISOLATE for memory isolation
void isolateVice(SendPort sendPort) {
  // The ISOLATE is memory isolated, the I value is defined by other isolates (default is the main ISOLATE environment) so null is obtained here
  print("Concurrent isolateVice() does not assign a value to I, so output I =" + i.toString()); / / output: -- - > null

  ReceivePort receivePort = new ReceivePort();   // The message receiver of the current ISOLATE
  // The second argument passed when the current function (isolateVice) is created (here we consider the sender of the ISO), using the sender of the master ISO to send the sender of its child ISO to complete the interaction
  sendPort.send(receivePort.sendPort);

  sendPort.send("I like you."); // Test sending messages to the main ISOLATE
  receivePort.listen((message) {
    print("Received message from main() ::" + message);
  });
}
Copy the code

event-driven

As we mentioned above, each ISOLATE is equivalent to a completely independent DART execution environment. If there are some tasks in the current environment, if they are executed in order, won’t some tasks take too long to be executed? In single-threaded languages, if you do not do any processing, this problem will indeed occur. People familiar with JS know that JS asynchronous operation is very good, the reason is that JS has a good event-driven task scheduling, improve the efficiency of task execution, especially the recent popular Node.js, is to do the extreme event-driven. Dart, as an excellent single-threaded language, cannot lack the excellent feature of event drivers. In DART, event drivers exist in ISOLates. In other words, each new ISOLATE has an independent and complete event-loop, and each loop contains two queues. One of the queues is calledMicrotask QueueThis queue has the highest execution priority, while the other queue **event queue ** is a common event queue. The two queues rely on fixed execution processes to complete the dart task execution mechanism. The event-driven execution flow chart is as follows:In the figure above, we can clearly see the subtle execution flow between the two queues:

  • The execution of the microtask-queue takes precedence over the execution of the event-queue, and the execution of the microtask-queue is polling. That is, the execution of the tasks in the Microtask-queue is not performed until all the tasks in the event queue are completed
  • The task in event-queue has the lowest execution level. After each task is executed, the microtask-queue is polled again. If there is a new task in the microtask-queue at this time, needless to say, The microtask-queue must be fully executed again and then returned to the event-queue

In addition, most of the tasks we normally execute are executed in event-queue, so if we want to execute normal tasks, we should try not to add too many complex business operations to microtask-queue. But at the same time, we can also see that “queue jumping” mechanism exists in DART. That is, we want a task to be executed before other tasks. We can choose to put the task in a microtask-queue first. Let’s look at an example:

import 'dart:io';

void main(){
  new File("C:\Users\Administrator\Desktop\ HTTP generic class.txt").readAsString().then((content){
      print(content);// It should print every line in the file, but it doesn't
  });
  while(true){}
}
Copy the code

As you can see from the result of the above case, the program blocks and never executes the contents of the file IO output. Why? The reason for this is simple: the IO stream is asynchronous, and the than method adds tasks to an event-queue. Main’s while loop executes before the FILE IO and blocks. Therefore, it is important to properly allocate microtask-queue and event-queue in DART. Also, because asynchronous tasks are used here, the execution order of the task queue changes, so the proper use of synchronous and asynchronous tasks is particularly important in DART development. Next we’ll look at asynchronous tasks and executor futures in DART

For more information, please referdart:isolate library documentation