Dart uses Computers and workers
When heavy computing tasks are performed, the App needs to use the ISOLATE or Worker to maintain timely response to user operations. 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 of the Dart code is thereisolationRun in computers, 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. It involves performing multiple tasks simultaneously
Dart is a single-threaded language, just like JS, so there is no multi-threading 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 belongs to a new 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, you can create multiple ISOLates to do so
Dart: ISOLATE **** package is the DART solution for capturing single-threaded DART code and allowing applications to make more use of available hardware
So how does the ISOLATE interact with each other? How did the isolate manage its own tasks?
Isolate.spawn
Spawn method source
Future<Isolate> spawn<T>(
void entryPoint(
T message
),
T message,
{bool paused = false.bool errorsAreFatal = true,
SendPort? onExit,
SendPort? onError,
@Since("2.3") String? debugName}
)
Copy the code
Let me give you 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!! ');
}
Copy the code
The spawn method of the Isolate class helps to run function foo in parallel with the rest of our code. The spawn function takes two arguments
- The function name
- Transfer object
- 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 chart
Isolate 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 for communication between the two. Isolate Examples of interaction:
import 'dart:isolate';
num? i;
void main() {
i = 2;
late SendPort childSendPort;
print('Main () because I was assigned$i');
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 rp = new ReceivePort();
Isolate.spawn(isoVice, rp.sendPort);
// The main process receives messages from the ISOLATE that holds the main process sender
rp.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('===== receives sender of function isoVice() =====');
} 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 isoVice(SendPort sp) {
// 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('concurrency isoVice() does not assign I, so output I =' + i.toString());
ReceivePort rp = ReceivePort(); // The message receiver of the current ISOLATE
sp.send(rp
.sendPort); // 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
sp.send('I like you'); // Test sending messages to the main ISOLATE
rp.listen((message) {
print('Received function main() message:' + 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. The microTask Queue, which has the highest execution priority, and the Event Queue, which is a common event queue, rely on a fixed execution process to complete the DART task execution mechanism. The event-driven execution flow is as follows:
From 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.