Dart series: the secret of dart excellence - isolation mechanism

Posted by cshaul on Sat, 01 Jan 2022 23:51:20 +0100

brief introduction

Many asynchronous programming skills in dart were introduced before. I wonder if you have found a problem. If it is asynchronous programming in java, locking and concurrency mechanisms will certainly be mentioned, but for dart, it seems that you have never heard of multithreading and concurrency. Why?

Today, let's explain the isolation mechanism in dart, and you will understand it.

Isolation mechanism in dart

Dart is a single threaded language, but as a single threaded language, dart supports asynchronous features such as future and stream. All this is the result of isolation mechanism and event cycle.

Let's first look at the isolation mechanism in dart.

Isolation refers to a specific space where dart runs. This space has separate memory and a single threaded event loop.

As shown in the figure below:

In other languages such as java or c + +, multiple threads share memory space. Although it brings a convenient way of concurrency and data communication, it also makes concurrent programming difficult.

Because we need to consider the synchronization of data between multiple threads, there are many additional locking mechanisms. People who know or have used them in detail should be very annoyed.

The biggest defect of multithreading is that programmers are required to have good logic thinking and programming skills, so that they can design multithreaded programs that run perfectly.

But in dart, none of this is a problem. All threads in dart have their own running space. The job of this thread is to run event loops.

So the problem comes. The main thread is processing the event loop, but what if it encounters a very time-consuming operation? If you run directly in the main thread, it may cause the main thread to block.

Dart also fully considers this problem, so dart provides an Isolate class to manage isolation.

Because the dart program itself runs in an Isolate, if an Isolate is defined in dart, it usually represents another Isolate that needs to communicate with the current Isolate.

Generate an Isolate

So how to generate an Isolate in the current dart program?

Isolate provides three generation methods.

A very common method is Isolate's factory method: spawn:

  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});

spawn will create a new Isolate. To call it, you need to pass in several parameters:

entryPoint represents the function to be called when generating a new Isolate. entryPoint accepts a message parameter. Generally speaking, message is a SendPort object used for communication between two isolates.

paused indicates whether the newly generated Isolate is in a suspended state. It is equivalent to:

isolate.pause(isolate.pauseCapability)

If you need to cancel the pause state later, you can call:

isolate.resume(isolate.pauseCapability)

errorsAreFatal corresponds to the seterrorsdata method.

onExit corresponds to addonexitlistener, and onerror corresponds to addErrorListener.

debugName refers to the name displayed by Isolate during debugging.

If a spawn error occurs, an IsolateSpawnException exception will be thrown:

class IsolateSpawnException implements Exception {
  /// Error message reported by the spawn operation.
  final String message;
  @pragma("vm:entry-point")
  IsolateSpawnException(this.message);
  String toString() => "IsolateSpawnException: $message";
}

The spawn method generates the same Isolate as the current code. If you want to use different codes to generate, you can use spawnUri to generate different codes by passing in the corresponding Uri address.

external static Future<Isolate> spawnUri(
      Uri uri,
      List<String> args,
      var message,
      {bool paused = false,
      SendPort? onExit,
      SendPort? onError,
      bool errorsAreFatal = true,
      bool? checked,
      Map<String, String>? environment,
      @Deprecated('The packages/ dir is not supported in Dart 2')
          Uri? packageRoot,
      Uri? packageConfig,
      bool automaticPackageResolution = false,
      @Since("2.3")
          String? debugName});

Another way is to use the Isolate constructor:

Isolate(this.controlPort, {this.pauseCapability, this.terminateCapability});

It has three parameters. The first parameter is controlPort, which represents the control right of another isolate. The latter two capabilities are subsets of the original isolate, indicating whether there is pause or terminate permission.

The general usage is as follows:

Isolate isolate = findSomeIsolate();
Isolate restrictedIsolate = Isolate(isolate.controlPort);
untrustedCode(restrictedIsolate);

Interaction between Isolate

All dart code runs in isolate, and then the code can only access class and value in the same isolate. The communication between multiple isolates can be realized by ReceivePort and SendPort.

Let's take a look at SendPort. SendPort is a kind of Capability:

abstract class SendPort implements Capability 

SendPort is used to send a message to ReceivePort. There are many types of messages, including:

Null, bool,int,double,String,List,Map,TransferableTypedData,SendPort and Capability.

Note that the send action is done immediately.

In fact, SendPort is created by ReceivePort. A ReceivePort can receive multiple sendports.

ReceivePort is a type of Stream:

abstract class ReceivePort implements Stream<dynamic>

As a Stream, it provides a listen to process received messages:

  StreamSubscription<dynamic> listen(void onData(var message)?,
      {Function? onError, void onDone()?, bool? cancelOnError});

An example

After talking about so many principles, some students may ask, so how to use it?

Here's an example:

import 'dart:isolate';

var isolate;

void entryPoint(SendPort sendPort) {
  int counter = 0;
  sendPort.send("counter:$counter");
}
void main() async{
  final receiver = ReceivePort();
  receiver.listen((message) {
    print( "Message received $message");
  });
  isolate = await Isolate.spawn(entryPoint, receiver.sendPort);
}

In the main thread, we created a ReceivePort and then called its listen method to listen to the messages sent by sendPort.

Then spawn comes up with a new Isolate, which will call entryPoint after initialization.

In this entryPoint method, sendPort is used to send a message to ReceivePort.

Final run, print:

Message received counter:0

summary

The above is the isolation mechanism in dart and the use of Isolate.

This article has been included in http://www.flydean.com/25-dart-isolates/

The most popular interpretation, the most profound dry goods, the most concise tutorial, and many tips you don't know are waiting for you to find!

Welcome to my official account: "those things in procedure", understand technology, know you better!

Topics: Java Flutter dart