1
头图

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 and Stream .
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 with async 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"), the compute() 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:

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:

  1. Before generating the isolate, the code creates a ReceivePort so that the isolate work object can pass information to the main isolate.
  2. 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 new SendPort , but uses the ReceivePort of sendPort property.
  3. After Isolate is initialized, the main isolate starts waiting for its result. Since ReceivePort implements Stream , 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.15

In 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


Flutter
350 声望2.5k 粉丝

Flutter 为应用开发带来了革新: 只要一套代码库,即可构建、测试和发布适用于移动、Web、桌面和嵌入式平台的精美应用。Flutter 中文开发者网站 flutter.cn