The Dart single-threaded
Dart event loops are single-threaded, providing a smooth and secure experience. The core is divided into main thread, microtask, and macro task. The main thread mainly includes service processing, network I/O, local file I/O, and asynchronous events. Dart has two event queues in a single thread, a microtask queue and an event queue.
- Microtask queue
Microtask queues contain microtasks within Dart and are scheduled using scheduleMicrotask.
- The event queue
Event queues contain external events, such as I/O, Timer, draw events, and so on.
Event loop
The chain specifies the order of events
If your code has dependencies, it is best to make them explicit. This helps developers understand your code and makes it more robust.
Error adding a task to an event queue:
future.then(... set an important variable...) ; Timer.run(() {... use the important variable... });Copy the code
True:
future.then(... set an important variable...) .then((_) {... use the important variable... });Copy the code
In the Dart single thread, their results are consistent, but not as easy to understand.
How to Create a Task
When you want to execute your code later in a task, you can use the Future class, which adds tasks to the end of the event queue, or the top-level scheduleMicrotask function, which adds tasks to the end of the microtask queue.
WhenComplate () instead of then for better use, or if you need to execute it when an error occurs.
How do I add tasks to the event queue
You can use either Timer or Future
You can also use Timer to schedule tasks, but if any uncaught exceptions occur in the task, the application will exit. Instead, we recommend using a Future, which builds on a Timer and adds features such as detecting task completion and responding to errors.
Add code to the event queue
new Future(() {
/ /... code goes here...
});
Copy the code
You can use then or whenComplate to perform later tasks. Look at this example
new Future(() => 21)
.then((v) => v*2)
.then((v) => print(v));
Copy the code
If you want to do this later, use future.delayed ():
new Future.delayed(const Duration(seconds:1), () {
/ /... code goes here...
});
Copy the code
Now that you know the basics, how do you put them together?
Use the task
With single threads and queues, there must be loops so that different queue tasks can be executed and events processed, so let’s look at loops.
- Enter the
main
Function and generate the corresponding microtask and event queue - Determine whether there are microtasks. If there are, they will be executed. If there are not, they will continue. After the execution, determine whether there are more microtasks. If there are, perform them. If not, continue
- If there is no executable microtask, the system determines whether there is an event task. If there is an event task, the system executes it. If there is no event task, the system returns to determine whether there is an event task
- New microtasks and event tasks can also be generated in microtasks and event tasks, so it is necessary to judge whether there are new microtasks and event tasks again.
To verify this, let’s look at the following code:
void main() {
test();
}
/ / / the task
/ / / timer
void test() async {
print('start');
scheduleMicrotask(() {
print('Microtask 1');
});
Future.delayed(Duration(seconds: 0)).then((value) {
print('Future 1');
});
Timer.run(() {
print('Timer 1');
});
print('end');
}
Copy the code
The operation process is as follows:
- First start
main
Function, printstart
- perform
scheduleMicrotask
Microtasks, adding tasks to the microtask queue - perform
Future
Event to add a task to the event queue - perform
timer
Event to add a task to the event queue - Perform event printing
end
- The second loop checks whether there are microtasks. The microtasks have been added, and now the microtasks are executed, printing
Microtask 1
- Check whether there are event tasks, which were added just now
Future
Task to perform printingFuture 1
- Check whether there are event tasks, which were added just now
Timer 1
Task to perform printingTimer1
The output
start
end
Microtask 1
Future 1
Timer 1
Copy the code
Look at the following example to prove that Timer and Future are one type of event.
/ / / the task
/ / / timer
void test2() async {
print('start');
scheduleMicrotask(() {
print('Microtask 1');
});
Timer.run(() {
print('Timer 1');
});
Future.delayed(Duration(seconds: 0)).then((value) {
print('Future 1');
});
print('end');
}
Copy the code
The output
start
end
Microtask 1
Timer 1
Future 1
Copy the code
In test and test2 above, the Timer and Future positions are reversed, which means that the task of adding events is reversed in order and executed in reverse order. Let’s look at Future source code:
factory Future.delayed(Duration duration, [FutureOr<T> computation()]) {
_Future<T> result = new _Future<T>();
new Timer(duration, () {
if (computation == null) {
result._complete(null);
} else {
try {
result._complete(computation());
} catch(e, s) { _completeWithErrorCallback(result, e, s); }}});return result;
}
Copy the code
The essence of the future.delayed source code is to add a task to a Timer and run the task after a specified time.
After executing the event, you need to determine whether there are microtasks again
The priority of the microtask queue is higher than that of the event queue. Therefore, every time a task is executed, it first checks whether there are unexecuted microtasks.
After the event queue is executed, new microtasks may be added to the queue, so the microtask queue needs to be scanned again. Look at the following example:
void test3() async {
print('start'); / / 1
scheduleMicrotask(() {/ / 2
print('Microtask 1');/ / 5
});
Timer.run(() {/ / 3
print('Timer 1');/ / 6
Timer.run(() {/ / 9
print('Timer 1 Microtask 2 ');/ / 10
});
scheduleMicrotask(() {/ / 7
print('Microtask 2');/ / 8
});
});
print('end');/ / 4
}
Copy the code
Order of execution:
- print
start
- Add a microtask to the queue
- add
Timer 1
To the event queue - Perform print
end
task - Determine if there are microtasks and find microtasks
Microtask 1
, execute immediately - Determine whether there are microtasks, and find whether there are event tasks, and find
Timer 1
Task and execute, add event taskTimer 1 Microtask 2
, add microtasksMicrotask 2
To the microtask queue - Determine if there are microtasks and find microtasks
Microtask 2
And execute. - Check whether event tasks exist and discover event tasks
Timer 1 Microtask 2
And executed.
Results output
start
end
Microtask 1
Timer 1
Microtask 2
Timer 1 Microtask 2
Copy the code
Will microtasks or event tasks get stuck?
According to the previous understanding, the event task is executed after the micro-task is executed. When an event takes a long time to process, the subsequent tasks will always be in the waiting state. When there are enough tasks, it still takes some time to complete them.
void test4() async {
print('start ${DateTime.now()}');
for(int i =0; i < 99999; i++){ scheduleMicrotask(() {print('Microtask 1');
});
}
Timer.run(() {
print('Timer 1 ${DateTime.now()}');
});
print('end ${DateTime.now()}');
}
Copy the code
Output:
Start 2020-07-28 17:44:11.561886 end 2020-07-28 17:44:11.593989... Microtask 1 ..... The Timer 1 2020-07-28 17:44:11. 893093Copy the code
It can be seen that the execution time of these tasks basically reached 0.33 seconds.
When a thread is running out of processing tasks, another thread needs to be started.
Isolates multithreading
In DART, threads are called turbo threads. Each thread does not share memory and communicates through a messaging mechanism.
Let’s look at an example of using Dart to achieve multithreading.
void test5()async{
final rece = ReceivePort();
isolate = await Isolate.spawn(sendPort, rece.sendPort);
rece.listen((data){
print(${data},name:$name);
});
}
void sendPort(SendPort sendPort){
sendPort.send('Send message');
}
Isolate isolate;
String name='fgyong';
void main() {
test(5); }Copy the code
The output
The send message name: fGYong was receivedCopy the code
How to deal with multi-thread communication?
After the thread is created, the child thread needs to send a port and a message to the main thread. The main thread records this port and uses this port to communicate with the child thread next time.
The specific code is as follows:
/// the new thread executes a new task and listens
Isolate isolate;
Isolate isolate2;
void createTask() async {
ReceivePort receivePort = ReceivePort();
isolate = await Isolate.spawn(sendP1, receivePort.sendPort);
receivePort.listen((data) {
print(data);
if (data is List) {
SendPort subSencPort = (data as List) [1];
String msg = (data as List) [0];
print('$MSG received on main thread ');
if (msg == 'close') {
receivePort.close();
} else if (msg == 'task') {
taskMain();
}
subSencPort.send(['Main thread issued']); }}); }void sendP1(SendPort sendPort) async {
ReceivePort receivePort = new ReceivePort();
receivePort.listen((data) async {
print(data);
if (data is List) {
String msg = (data as List) [0];
print('$MSG received in child thread ');
if (msg == 'close') {
receivePort.close();
} else if (msg == 'task') {
var m = await task();
sendPort.send(['$m', receivePort.sendPort]); }}}); sendPort.send(['Child thread thread emitted', receivePort.sendPort]);
}
Future<String> task() async {
print('Child thread executes task');
for (var i = 0; i < 99999999; i++) {}
return 'task to complete;
}
void taskMain() {
print('Main thread executes task');
}
Copy the code
Output:
SendPort sent by the child thread received on the main thread. Sent by the main thread Received on the child threadCopy the code
See the code base for more child threads interacting with the main thread
Complex problem solutions
Imagine a project that requires two teams to complete and involves multiple tasks. It can be divided into two high-priority tasks (a high-priority task generates two tasks, one urgent and one non-urgent) and two non-high-priority tasks (a non-high-priority task generates two tasks, one urgent and one non-urgent). There is also one that you have to rely on other teams to do because the team doesn’t have the resources to do that, and a third party can also produce a high-priority task and a low-priority task.
According to the urgent tasks as micro tasks, non-urgent tasks as event tasks are arranged, and the third party is the newly opened thread
The main task | High priority (microtasks) | Low priority (event task) | A third party (Isolate) |
---|---|---|---|
H1 | H1-1 | L1-2 | no |
H2 | H2-1 | L2-2 | no |
L3 | H3-1 | L3-2 | no |
L4 | H4-1 | L4-2 | no |
I5 | IH5-1 | I5-2 | is |
void test6() {
createTask();// Create a thread
scheduleMicrotask(() {// The first microtask
print('H1');
scheduleMicrotask(() {// First urgent task
print('H1-1');
});
Timer.run(() {// First non-urgent task
print('L1-2');
});
});
scheduleMicrotask(() {// The second high-priority task
print('H2');
scheduleMicrotask(() {// Second urgent task
print('H2-1');
});
Timer.run(() {// Second non-urgent task
print('L2-2');
});
});
Timer.run(() {// The first low-priority task
print('L3');
scheduleMicrotask(() {// Third urgent task
print('H3-1');
});
Timer.run(() {// Third non-urgent mission
print('L3-2');
});
});
Timer.run(() {// Second low priority task
print('L4');
scheduleMicrotask(() {// Urgent task number four
print('H4-1');
});
Timer.run(() {// The fourth non-urgent task
print('L4-2');
});
});
}
/// the new thread executes a new task and listens
Isolate isolate;
void createTask() async {
ReceivePort receivePort = ReceivePort();
isolate = await Isolate.spawn(sendPort, receivePort.sendPort);
receivePort.listen((data) {
print(data);
});
}
/// The new thread executes the task
void sendPort(SendPort sendPort) {
scheduleMicrotask(() {
print('IH5-1');
});
Timer.run(() {
print('IL5-2');
});
sendPort.send('The third party has completed the task');
}
Copy the code
The results
H1 H2 H1-1 H2-1 L3 H3-1 L4 H4-1 L1-2 L2-2 L3-2 L4-2 IH5-1 IL5-2 The third-party task is completedCopy the code
It can be seen that the items starting with H are of high priority and those starting with L are of low priority. The basic high-priority items are run before the low-priority items, as expected.
But why was the third party implemented at the end?
Since the creation of the thread requires events, and other tasks are too time-consuming, we can do another task with long events.
createTask(); // Create a threadfor (var i = 0; i < 9999999999; i++) {
}
...
Copy the code
Output:
Ih5-1 IL5-2 H1 H2 H1-1 H2-1 Third-party task End L3 H3-1 L4 H4-1 L1-2 L2-2 L2-2 L3-2 L4-2Copy the code
whyThe third-party task is completeRight in the middle? Not above H1? ?
Because this event is a low priority, whereas H is a high priority task.
whyThe third-party task is complete ??
It’s above L3. It’s not at the bottom, it has to be at the top of the lower priority queue?
The duration of the start operation event is too long. As a result, the third-party task has been executed before all tasks are executed. Therefore, the third-party task is added to the low-priority task queue first after the completion of the task.
If the number of time-consuming operations is small, the sequence of adding tasks is uncertain.
conclusion
Dart separates asynchrony from multithreading, where asynchrony is simply the result of multi-event training in an event loop, whereas multithreading can be understood as true concurrency (multiple threads working at the same time). In a single thread, it is divided into microtasks and other event queues, and the priority of microtasks queue is higher than other event queues.
reference
- Flutter is quick to use and learn
- The Event Loop and Dart
- Code base view demo