Welcome to WeChat official account: FSA full stack operation 👋
1, Dart asynchronous
Like JavaScript, Dart is a single thread model based on event loop mechanism, so there is no multithreading in Dart, so there is no distinction between main thread and sub thread
1. Synchronous and asynchronous
- Synchronization: in the same thread, execute from top to bottom according to the code writing order (intuitive feeling: need to wait)
- Asynchronous: during code execution, the execution of a certain section of code will not affect the execution of subsequent codes (intuitive feeling: no need to wait)
2. Single thread model
Single threaded model:
- On an execution line, only one task (event) can be executed at the same time, and other tasks must queue up behind to be executed
- In order not to hinder the execution of the code, every time-consuming task encountered will be suspended and put into the task queue. After the execution is completed, the tasks on the queue will be executed in order, so as to achieve the asynchronous effect
Advantages of single thread model and multi thread model:
- Advantages of single thread model: it avoids the disadvantages of multithreading and is more suitable for time-consuming operations that need to wait for the other party to transmit data or return results, such as network request, IO operation, etc
- The advantage of multithreading: make use of the multi-core of the processor to realize the computing intensive operation of parallel computing
Disadvantages of multithreading:
- It will bring additional resource and performance consumption
- Lock control is required when multiple threads operate shared memory. Lock competition will reduce performance and efficiency, and it is easy to cause deadlock in complex situations
3. Event cycle mechanism
You don't know when or in what order user clicks, swipes, or hard disk IO accesses occur, so you have to have a never-ending and non blocking loop to deal with these "emergencies" Therefore, the single thread model based on event loop mechanism appears:
The Dart event loop mechanism consists of one message loop and two message queues: event queue and microtask queue
Event Looper
After Dart executes the main function, the Event Looper starts working. The Event Looper takes priority to complete all the events in the Microtask Queue. The event in the Event Looper will not be executed until the Microtask Queue is empty. When the Event Looper is empty, the loop can be exited
Note: when Event Looper is empty, you can exit, but not necessarily, depending on the scene
Event Queue
The event of Event Queue comes from external events and Future
- External events: such as input / output, gesture, drawing, timer, Stream, etc
- Future: used to customize Event Queue events
For external events, once there is no microtask to execute, Event Looper will consider listing as the first item in the queue and execute it
Add an event to the Event Queue through the Future instance:
Future(() { // Event task });
Microtask Queue
- Microtask Queue takes precedence over Event Queue
- Usage scenario: you want to complete some tasks later, but you want to execute them before the next event
Microtask is generally used for very short internal asynchronous actions, and the number of tasks is very small. If there are too many microtasks, the Event Queue will not be in line, and the execution of the Event Queue will be blocked (for example, the user clicks and does not respond) Therefore, Event Queue is preferred in most cases, and the whole fluent source code only references the scheduleMicroTask() method seven times
Add a task to the Microtask Queue through the scheduleMicroTask() function:
scheduleMicrotask(() { // Micro task });
2, Future
Asynchronous operations in Dart mainly use future and async/await, which are similar to promise and async/await in front-end ES6. Future can be understood as a class with callback effect
1. Basic use
By looking at the constructor of Future, you know that a function with a return value type of futureor < T > needs to be passed in during creation:
factory Future(FutureOr<T> computation()) { ... }
This futureor < T > is a union type, and the final type may be Future or a specific type of generic t When generic t is not specified, the instance type is Future < dynamic > The following is an example of simulating network time-consuming requests:
Future<String> getNetworkData() { // 1. Wrap the time-consuming operation into the callback function of Future return Future<String>(() { sleep(Duration(seconds: 2)); return "Hello lqr"; // As long as there is a return result, the callback of then corresponding to Future will be executed (equivalent to promise resolve) // throw Exception("error"); // If no result is returned (with error message), you need to throw an exception in the Future callback (equivalent to promise reject) }); }
There are three common methods for Future instances:
- then((value) {...}): executed during normal operation
- Catchrror ((ERR) {...}): execute when an error occurs
- whenComplete(() {...}): it will be executed whether it is successful or not
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. Get the results var future = getNetworkData(); future.then((value) => print(value)) // Hello lqr .catchError((err) => print(err)) .whenComplete(() => print("Execution complete")); // It will be executed whether it is successful or not print("main end"); }
The log output is as follows:
main start main end // Output after 2 seconds: Hello lqr Execution complete
Note that the above three methods can be written separately, but each time a method is executed, the future instance needs to be re assigned (equivalent to a layer of package), otherwise the subsequent methods are invalid:
var future = getNetworkData(); // Incorrect writing: future.then((value) => print(value)); future.catchError((error) => print(error)); // invalid // Correct writing: future = future.then((value) { print(value); return value; }); future.catchError((error) => print(error)); // Effective
2. Chain call
Future can return another future instance in the then() method to achieve the effect of chain call, which is very useful for those network requests with data association:
main(List<String> args) { print("main start"); // Chain call to execute 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"); }
Emphasis: the Future constructor requires that a function whose return value type is futureor < T > be passed in, but because futureor < T > is a union type, another Future instance or a specific type of data, such as string, can be returned here
3. Other API s
In addition to the default constructor, Future also provides several common named constructors:
- Future.value(): create a future instance that returns specific data
- Future.error(): create a future instance that returns an error
- Future.delayed(): create a delayed future instance
main(List<String> args) { print("main start"); Future.value("Hello lqr").then((value) => print(value)); Future.error("Error ").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("Error ")) .catchError((error) => print(error)) .whenComplete(() => print("Execution complete")); // It will be executed whether it is successful or not print("main end"); }
3, async/await
async/await is a syntax sugar provided by Dart that can realize asynchronous calling process in synchronous code format
1. Basic use
- await must be used in async functions
- The result returned by async function must be a Future
Future getNetworkData() async { var userId = await getUserId(); var userInfo = await getUserInfo(userId); return userInfo.username // It will be automatically wrapped into Future }
If async/await is not used, the above code needs to be written as follows:
Future getNetworkData() { return getUserId().then((userId) { return getUserInfo(userId); }).then((userInfo) { return userInfo.username; }); }
In contrast, the code written in async/await will be clearer in understanding
4, isolate
All Dart codes are run in isolate, which is a small space on the machine with its own private memory block and a single thread running Event Looper Each isolate is isolated from each other and does not share memory like threads Generally, a Dart application can only run all codes in one isolate, but if there are special needs, you can open multiple:
Note: Dart does not have the concept of thread, only isolate
1. Create isolate (Dart API)
Dart provides isolate.com by default Spawn (entryPoint, message) is used to start isolate. You can know from the source code that the formal parameter message is actually the parameter required for the execution of the function corresponding to the formal parameter entryPoint:
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});
Use isolate Spawn (entrypoint, message) starts isolate and specifies the task to be executed:
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); }
2. isolate communication (unidirectional)
The only way isolates can work together is by passing messages back and forth Generally, the child isolate will send the running result to the main isolate in the form of message through the pipeline and process the message in the Event Looper of the main isolate. At this time, you need to use ReceivePort to process the message delivery:
- When the child isolate is started, the send port of the main isolate is passed to the child isolate as a parameter
- When the child isolate completes execution, it can send information to the main isolate through the pipeline (SendPort)
import 'dart:isolate'; main(List<String> args) async { print("main start"); // 1. Create a pipe var receivePort = ReceivePort(); // 2. Create an isolate Isolate isolate = await Isolate.spawn(foo, receivePort.sendPort); // 3. Monitoring pipeline receivePort.listen((message) { print(message); // Close the pipe when it is no longer in use receivePort.close(); // Kill isolate when no longer in use isolate.kill(); }); print("main end"); } void foo(SendPort sendPort) { sendPort.send("Hello lqr"); }
The above only realizes the one-way communication of isolate. The two-way communication is troublesome. If you are interested, you can check some other materials
3. Create isolate (fluent API)
Fluent provides a more convenient API for opening isolate: compute() function The following is sample code:
main(List<String> args) async { int result = await compute(powerNum, 5); print(result); } int powerNum(int num) { return num * num; }
compute() is the API of fluent, not Dart. Therefore, the above code can only be run in the fluent project