Welcome to follow the official wechat account: FSA Full stack action 👋
Dart is asynchronous
Dart, like JavaScript, is a single-threaded model based on an event loop, so there is no multi-threading in Dart, which means there is no main thread or child thread.
1. Synchronous and asynchronous
- Synchronization: Execute the code from top to bottom in the same thread in the order it was written (intuitive feeling: wait)
- Asynchronous: In code execution, the execution of one piece of code does not affect the execution of the following code (intuitive sense: no waiting)
2. Single-threaded model
Single-threaded model:
- Only one task (event) can be executed on one line at a time, and all other tasks must queue up behind.
- In order not to hinder the execution of the code, each time-consuming task encountered will be suspended and put into the task queue. After the execution, the tasks on the queue will be executed in sequence, so as to achieve the asynchronous effect.
Advantages of single-thread model and multi-thread:
- Advantages of the single-thread model: it avoids the disadvantages of multi-threading and is suitable for time-consuming operations that need to wait for each other to transmit data or return results, such as network requests and IO operations.
- Advantage of multithreading: as far as possible to use the processor’s multiple cores to achieve parallel computing intensive operations.
Disadvantages of multithreading:
- There are additional resource and performance costs.
- Lock control is required when multiple threads operate shared memory. Lock competition reduces performance and efficiency, and in complex cases, it is easy to cause deadlocks.
3. Event loop mechanism
You don’t know when or in what order user clicks, swipes, hard drive I/O accesses, etc., so there’s a never-ending, unblocking loop waiting to deal with these “emergent” events. Thus, the single-threaded model based on the event loop mechanism emerged:
The Dart Event loop consists of an Event Looper and two Event queues: Event Queue and MicroTask Queue.
Event Looper
Dart will execute the Event Looper function after the main function is executed. Event Looper will execute all events in the Microtask Queue until the Microtask Queue is empty. The Event in Event Looper is executed, and the loop can be exited only when the Event Looper is empty.
Note: When the Event Looper is empty, it is possible, but not mandatory, to exit, depending on the situation.
Event Queue
Events in the Event Queue come from external events and futures
- External events: such as input/output, gesture, draw, timer, Stream, etc
- Future: Used to customize Event Queue events
For external events, once there are no microtasks to execute, the Event Loop considers the first item in the Event Queue and will execute it.
Add events to the Event Queue via the Future instance:
Future(() {
// Event task
});
Copy the code
Microtask Queue
Microtask Queue
Is of higher priority thanEvent Queue
.- Usage scenario: You want to complete some tasks (microtasks) later but want to execute them before the next event.
Microtasks are usually used for very short internal asynchronous actions with a small number of tasks. If there are too many microtasks, the Event Queue will not be queued and the execution of the Event Queue will be blocked (for example, the user clicks on the Event Queue and does not respond). Therefore, the Event Queue is preferred in most cases and the scheduleMicroTask() method is referenced only 7 times throughout the Flutter source code.
Add tasks to the Microtask Queue using scheduleMicroTask() :
scheduleMicrotask(() {
Micro / / task
});
Copy the code
Second, the Future
Asynchronous operations in Dart mainly use Future and async/await, which are generally similar to promises in front-end ES6. The use of async/await is similar. Future can be understood as a class with callback effects.
1. Basic use
By looking at the Future constructor, we know that we need to pass in a function that returns a value of type FutureOr
:
factory Future(FutureOr<T> computation()) {
...
}
Copy the code
The FutureOr
is a union type, and the final type may be Future or a concrete type of the generic T. When generic T is not specified, the instance type is Future
. Here is an example of a simulated network time-consuming request:
Future<String> getNetworkData() {
// 1. Wrap time-consuming operations in the Future's callback function
return Future<String>(() {
sleep(Duration(seconds: 2));
return "Hello lqr"; // Execute the Future's then callback (equivalent to promise-resolve) whenever a result is returned.
// throw Exception("error"); // If no result is returned (with an error message), throw an exception (equivalent to promise-reject) in the Future callback
});
}
Copy the code
There are three common methods for Future instances:
- then((value){… }): executed during normal runtime
- catchError((err){… }): execute when an error occurs
- whenComplete((){… }): executes regardless of success
The execution status and results of the Future instance can be obtained through the above three methods:
main(List<String> args) {
print("main start");
// 2
var future = getNetworkData();
future.then((value) => print(value)) // Hello lqr
.catchError((err) => print(err))
.whenComplete(() => print("Execution completed")); // Execute regardless of success
print("main end");
}
Copy the code
The following output is displayed:
Main start main end // Output after 2 seconds: Hello LQR The command is executedCopy the code
Note that the above three methods can be written separately, but the future instance needs to be reassigned each time a method is executed, otherwise the subsequent methods will not work:
var future = getNetworkData();
// Error:
future.then((value) => print(value));
future.catchError((error) => print(error)); / / is invalid
// The correct way to write:
future = future.then((value) {
print(value);
return value;
});
future.catchError((error) => print(error)); / / effective
Copy the code
2. Chain call
A Future can return another instance of the Future in the then() method to achieve the effect of a chained call, which is useful for network requests with data associated:
main(List<String> args) {
print("main start");
// a chain call to perform multiple data processing
Future(() {
return "First result";
}).then((value) {
print(value);
return "Second result";
}).then((value) {
print(value);
return "Third result";
}).then((value) {
print(value);
}).catchError((error) {
print(error);
});
print("main end");
}
Copy the code
Note: The Future constructor requires passing in a function that returns a value of type FutureOr
, but because FutureOr
is a union type, it can return another Future instance or a concrete type of data, such as a string.
3. Other apis
In addition to the default constructor, Future provides several commonly used named constructors:
- Future.value(): Creates a Future instance that returns concrete data
- Future.error(): Creates a Future instance that returns an error
- Future.delayed(): Creates a delayed Future instance
main(List<String> args) {
print("main start");
Future.value("Hello lqr").then((value) => print(value));
Future.error("Something went wrong.").catchError((error) => print(error));
Future.delayed(Duration(seconds: 3))
.then((value) => "Hello lqr")
.then((value) => print(value));
Future.delayed(Duration(seconds: 2), () = >"Hello lqr")
.then((value) => print("welcome"))
.then((value) => throw Exception("Something went wrong."))
.catchError((error) => print(error))
.whenComplete(() => print("Execution completed")); // Execute regardless of success
print("main end");
}
Copy the code
Third, async/await
Async /await is a syntactic sugar provided by Dart that enables asynchronous invocation in a synchronous code format.
1. Basic use
await
Must be inasync
Function.async
The result returned by the function must be a Future
Future getNetworkData() async {
var userId = await getUserId();
var userInfo = await getUserInfo(userId);
return userInfo.username // The Future will be wrapped automatically
}
Copy the code
If you are not using async/await, the above code should say:
Future getNetworkData() {
return getUserId().then((userId) {
return getUserInfo(userId);
}).then((userInfo) {
return userInfo.username;
});
}
Copy the code
In contrast, code written with async/await is clearer to understand.
Four, isolate
All Dart code runs on the ISOLATE, which is a small space on the machine with its own private block of memory and a single thread running Event Looper. Each ISOLATE is isolated from each other, unlike threads that can share memory. In general, a Dart application will only run all the code in one ISOLATE, but if there is a special need, more than one can be enabled:
Note: Dart does not have the concept of threads, only ISOLATE.
1. Create ISOLATE (Dart API)
Spawn (entryPoint, message) Dart provides the isolate. spawn(entryPoint, message) parameter to enable the Isolate.
external static 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
Start the Isolate using ISOLate. spawn(entryPoint, message) and specify tasks to execute:
import 'dart:isolate';
main(List<String> args) {
print("main start");
Isolate.spawn(calc, 100);
print("main end");
}
void calc(int count) {
var total = 0;
for (var i = 0; i < count; i++) {
total += i;
}
print(total);
}
Copy the code
2. Isolate Communication (one-way)
The only way the isolate can work together is by passing messages back and forth. Generally, the sub-ISOLATE sends the running result to the main isolate through a pipe as a message and processes the message in the Event Looper of the main ISOLATE. In this case, the ReceivePort is used to process the message transmission:
- In the start
Son to isolate
When willThe main isolate
Send pipe (SendPort
) as a parameter toSon to isolate
. Son to isolate
At the end of execution, you can use pipes (SendPort
) toThe main isolate
Send a message.
import 'dart:isolate';
main(List<String> args) async {
print("main start");
// 1. Create a pipe
var receivePort = ReceivePort();
// 2. Create the ISOLATE
Isolate isolate = await Isolate.spawn(foo, receivePort.sendPort);
// 3
receivePort.listen((message) {
print(message);
// Close the pipe when it is no longer in use
receivePort.close();
Kill the ISOLATE when it is no longer in use
isolate.kill();
});
print("main end");
}
void foo(SendPort sendPort) {
sendPort.send("Hello lqr");
}
Copy the code
The above only realized one-way communication of ISOLATE. Two-way communication is quite troublesome. If you are interested, you can check some other information.
Create the ISOLATE (Flutter API)
Flutter provides a more convenient API for enabling the ISOLATE: compute() function. Here is the sample code:
main(List<String> args) async {
int result = await compute(powerNum, 5);
print(result);
}
int powerNum(int num) {
return num * num;
}
Copy the code
Compute () is the Flutter API, not the Dart API, so the code above can only run in a Flutter project.
The resources
- Dart threads and asynchronism developed by Flutter
- Dart asynchronous programming: Isolates and event loops
- Futures – Isolates – Event Loop