Dart supports concurrent code programming through async-await, isolate, and some asynchronous type concepts (eg Future
and Stream
Future
async-await, 061e7aff9b1269 and Stream
A brief introduction is given, and the focus is on the explanation of isolate.
In the application, all Dart code runs isolate Each Dart isolate has its own running thread, and they cannot share mutable objects with other isolates. In scenarios where communication is required, isolate uses the message mechanism. Although Dart's isolate model is designed based on lower-level primitives such as processes and threads provided by the operating system, we will not discuss its specific implementation in this article.
Most Dart applications will only use one isolate (ie main isolate execute code in parallel on multiple processor cores.
Pay attention when using multiple platforms
All Dart applications can use async-await,
Future
andStream
.
And isolate is only implemented for native platform using .
Web applications built with Dart can use Web Workers achieve similar functionality.
Asynchronous Types and Syntax
If you are already Future
, Stream
and async-await, you can skip to the isolate section to read.
Future and Stream types
The Dart language and libraries provide functionality that will return some value in the future of the current call Future
and Stream
Take Promise in JavaScript as an example. In Dart, a int
should be declared as Future<int>
; a promise that will continue to return a series of int
should be declared as Stream<int>
.
Let's use dart:io
as another example. File
's synchronous method readAsStringSync() API documentation") will read the file in a synchronous call, blocking until the read is complete or an error is thrown. This will return String
, or throw an exception. And Its equivalent asynchronous method readAsString() Method API Documentation") will return an object of type Future<String>
At some point in the future, Future<String>
will end with a string or error.
Why does it matter whether a method is synchronous or asynchronous? Because most applications need to do a lot of things at the same time. For example, an application might make an HTTP request and make various UI updates to the user's actions before the request returns. Asynchronous code will help the application to maintain a more interactive state.
async-await syntax
async
and await
keywords are the way to use declarations to define asynchronous functions and get their results.
Here is an example of a synchronous code that blocks when calling file I/O:
void main() {
// Read some data.
final fileData = _readFileSync();
final jsonData = jsonDecode(fileData);
// Use that data.
print('Number of JSON keys: ${jsonData.length}');
}
String _readFileSync() {
final file = File(filename);
final contents = file.readAsStringSync();
return contents.trim();
}
Here's similar code, but instead calls asynchronously:
void main() async {
// Read some data.
final fileData = await _readFileAsync();
final jsonData = jsonDecode(fileData);
// Use that data.
print('Number of JSON keys: ${jsonData.length}');
}
Future<String> _readFileAsync() async {
final file = File(filename);
final contents = await file.readAsString();
return contents.trim();
}
main()
function uses the await
_readFileAsync()
, so that while the native code (file I/O) is executing, other Dart code (such as event handlers) can continue to execute. After using await
, Future<String>
type returned by the _readFileAsync()
call is also converted to String
. Thus, when assigning the result content
to a variable, it is implicitly converted to type String
await
keyword is only valid in functions withasync
defined before the function body.
As you can see in the image below, Dart code pauses when executing non-Dart code readAsString()
, both in the Dart VM and in the system. Dart code will continue to execute after readAsString()
If you want to know more about async
, await
and Future
, you can visit
Asynchronous programming codelab for learning.
How Isolate Works
Modern devices often use multi-core CPUs. In order to make programs perform better on devices, developers sometimes use threads that share content to run code concurrently. However, the shared state may produce race condition, causing errors ,
It may also increase the complexity of the code.
Dart codes don't run on multiple threads, instead they run inside an isolate. Each isolate will have its own heap memory to ensure that isolates are isolated from each other and cannot access state from each other.
Since such an implementation doesn't share memory, you also don't need to worry about mutexes and other locks ).
When using isolate, your Dart code can perform multiple independent tasks at the same time, and use the available processor cores. Isolate is similar to threads and processes, but each isolate has its own memory and a separate thread running the event loop.
main isolate
In general scenarios, you don't need to care about isolate at all. Usually a Dart application will execute all the code under the main isolate, as shown in the following figure:
Even an application with only one isolate can run smoothly as long as it uses async-await to handle asynchronous operations. An application with good performance will enter the event loop as soon as possible after a fast start. This allows applications to quickly respond to corresponding events through asynchronous operations.
Lifecycle of Isolate
As shown in the figure below, each isolate starts by running Dart code, such as the main()
function. Executed Dart code may register some event listeners, such as handling user actions or file reads and writes. When the Dart code executed by isolate ends, if it still needs to handle the events that have been listened to, it will continue to be held. After all events are processed, isolate exits.
event handling
In the client application, the event queue of the main isolate may contain redraw requests, click notifications or other interface events. For example, the figure below shows an event queue with four events, which processes events in a first-in, first-out fashion.
As shown in the figure below, after the main()
method is executed, the processing in the event queue starts, and the first redrawn event is processed at this time. The main isolate then handles the click event, followed by another repaint event.
If a synchronous operation takes a long time to process, the application appears to be unresponsive. In the image below, the code to handle the click event is time-consuming, resulting in the event that follows not being processed in a timely manner. At this time, the application may freeze, and all animations cannot be played smoothly.
In a client application, the synchronous operation takes too long, usually results in Caton animation . And worst of all, the app interface can become completely unresponsive.
Objects running in the background
If your application is affected by time-consuming computation and becomes stuck, for example, parsing a large JSON file ,
You can consider transferring time-consuming computations to separate work background running objects . The figure below shows a common scenario where you can generate an isolate that will perform computationally time-consuming tasks and exit when finished. This isolate work object will return the result when it exits.
Each isolate can pass an object through message communication, and all the contents of this object need to meet the conditions of being passable. Not all objects meet the delivery conditions, and when the conditions are not met, the message sending will fail.
For example, if you want to send a List<Object>
, you need to make sure that all elements in the list are passable. Suppose there is a Socket
this list, since it cannot be delivered, you cannot send the whole list.
You can consult the send() method Method API Documentation") to determine which types can be passed.
The Isolate worker object can perform I/O operations, set timers, and various other behaviors. It will hold its own memory space, isolated from the main isolate. This isolate will not affect other isolates when it is blocked.
code example
This section will focus on some examples of implementing isolate Isolate
Flutter Development
If you are developing with Flutter on a non-web platform, then instead of using the
Isolate
API directly, consider using the compute() method provided by method to hand off the work to a separate isolate"), thecompute()
method can be used in a simple way to A function call is encapsulated into the isolate work object.
Implement a simple isolate work object
This section will show the implementation of a main isolate and the isolate work object it generates. The Isolate work object executes a function, ends the object when complete, and sends the function result to the main isolate. (The compute() method provided by Flutter works in a similar fashion.)
The following examples will use these isolate-related APIs:
- Isolate.spawn() Method API Documentation ") and Isolate.exit() Method API Documentation ")
- ReceivePort and SendPort
The code for the main isolate is as follows:
void main() async {
// Read some data.
final jsonData = await _parseInBackground();
// Use that data
print('number of JSON keys = ${jsonData.length}');
}
// Spawns an isolate and waits for the first message
Future<Map<String, dynamic>> _parseInBackground() async {
final p = ReceivePort();
await Isolate.spawn(_readAndParseJson, p.sendPort);
return await p.first;
}
_parseInBackground()
method contains the generate the background isolate work object, and returns the result:
- Before generating the isolate, the code creates a
ReceivePort
so that the isolate work object can pass information to the main isolate. - Next is a call to
Isolate.spawn()
to generate and start an isolate worker object that runs in the background. The first parameter of this method is a reference to the function executed by the isolate work object:_readAndParseJson
.SendPort
that isolate uses to communicate with the main isolate. The code here is not create newSendPort
, but uses theReceivePort
ofsendPort
property. - After Isolate is initialized, the main isolate starts waiting for its result. Since
ReceivePort
implementsStream
, you can easily use the first property to get the single message returned by the isolate work object.
The initialized isolate will execute the following code:
Future _readAndParseJson(SendPort p) async {
final fileData = await File(filename).readAsString();
final jsonData = jsonDecode(fileData);
Isolate.exit(p, jsonData);
}
After the last line of code, isolate will exit, sending jsonData
over the incoming SendPort
.
, data copying usually occurs, and the time spent varies with the size of the data, and the complexity is O 161e7aff9b19ff (n) . However, when you Isolate.exit()
, the message held in the isolate is not copied, but directly transferred to the receiving isolate. Such a transfer is very fast, and the time complexity is only O (1) .
Isolate.exit()
was introduced in Dart 2.15In previous Dart versions, only explicit messaging was
Isolate.send()
This will be explained in the example in the next subsection.
The following diagram illustrates the communication flow between the main isolate and the isolate worker object:
Send message content multiple times between isolates
If you want to establish more communication between SendPort
, then you need to use the send() method of method API documentation "). The following figure shows a common scenario, the master isolate will send a request message to the isolate job objects, and then continue to communicate multiple times between them, making requests and replies.
isolate example listed below includes the usage of sending multiple messages:
- send_and_receive.dart shows how to send a message from the main isolate to the generated isolate. Closer to the previous example.
- long_running_isolate.dart shows how to generate a long-running isolate that sends and receives messages multiple times.
properties and isolate groups
When an isolate calls Isolate.spawn() method API documentation "), two isolates will have the same execution code and will be grouped into the same isolate group . The Isolate group will bring performance optimizations, such as new The isolate will run the code held by the isolate group, that is, the shared code call. Meanwhile, Isolate.exit()
is only valid when the corresponding isolate belongs to the same group.
In some cases, you may need to use the Isolate.spawnUri() method API documentation") to generate a new isolate with the executed URI and include a copy of the code. However, spawnUri()
will be spawn()
slower than 061e7aff9b1bb6, and the newly generated The isolate will be in a new isolate group. Also, when isolates are in different groups, message passing between them will be slower.
in Flutter development
Flutter does not support
Isolate.spawnUri()
.
Article information
- Original: Dart official document " Concurrency in Dart "
- Translation: @AlexV525
- Reviewers: @Vadaski, @Nayuta403
- Chinese document: dart.cn/guides/language/concurrency
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。