Dart - Spawn, Kill & Send Message to Isolate

How to use Isolate in Dart, from spawning a new one, sending message, getting result and killing it. Find out on this tutorial.

Dart is a single-threaded language. But it doesn't mean we can't run code in parallel. Dart has Isolate which allows us to run multiple things in parallel.

Spawn an Isolate

Below is Dart's static method for spawning an Isolate which is available if you've added import 'dart:isolate';

  external static Future spawn(
        void entryPoint(T message), T message,
        {bool paused: false,
        bool errorsAreFatal,
        SendPort onExit,
        SendPort onError});

It only has two required parameters entryPoint and message. The entryPoint is a top-level function or a static method that has one parameter. It can't be an instance method or a value of function expressions.

The message will be passed to the entryPoint function, so it's is required that the message and the function parameter has the same type.

NameTypeDescription
pausedbool

If true, the isolate will start up in a paused state.

errorsAreFatalbool

If true, uncaught errors will terminate the isolate.

onExitSendPort

Requests an exit message on [responsePort] when the isolate terminates.

onErrorSendPort

Requests that uncaught errors of the isolate are sent back to [port].

Kill an Isolate

After an isolate has finished its task, we can request to terminate it. Isolate has kill method.

  external void kill({int priority: beforeNextEvent});

Optionally, you can pass an argument priority whose value if provided must be immediate or beforeNextEvent event. If the value is immediate, the isolate shuts down as soon as possible. If the value is beforeNextEvent, it will be scheduled to shutdown the next time control returns to the event loop.

Example 1

In this first example, we spawn some isolates with String message. The entryPoint function (runSomething) prints the message first, then call an API and print the response. If you run the script, you'll see that the isolates run in parallel (the argument on all isolates will be printed first before any isolate gets the response).

  import 'dart:convert';
  import 'dart:io';
  import 'dart:isolate';
  
  List<Isolate> isolates;
  
  void start() async {
    isolates = new List();
  
    isolates.add(await Isolate.spawn(runSomething, 'first'));
    isolates.add(await Isolate.spawn(runSomething, 'second'));
    isolates.add(await Isolate.spawn(runSomething, 'third'));
  }
  
  void runSomething(String arg) async {
    print('arg: $arg');
    var request = await HttpClient().getUrl(Uri.parse('https://swapi.co/api/people/1'));
    var response = await request.close();
  
    await for (var contents in response.transform(Utf8Decoder())) {
      print(contents);
    }
  }
  
  void stop() {
    for (Isolate i in isolates) {
      if (i != null) {
        i.kill(priority: Isolate.immediate);
        i = null;
        print('Killed');
      }
    }
  }
  
  void main() async {
    await start();
  
    print('Press enter to exit');
    await stdin.first;
  
    stop();
    exit(0);
  }

Example 2

In the second example, we want to collect the result of each isolate. To do so, we can create an instance of ReceivePort. It has sendPort property of type SendPort, which allows messages to be sent to the receive port. The receive port needs to listen for data using listen method whose parameter is a function. To send a message to the receive port, use send method of SendPort.

  import 'dart:convert';
  import 'dart:io';
  import 'dart:isolate';
  
  List<Isolate> isolates;
  
  void start() async {
    isolates = new List();
    ReceivePort receivePort= ReceivePort();
  
    isolates.add(await Isolate.spawn(runSomething, receivePort.sendPort));
    isolates.add(await Isolate.spawn(runSomething, receivePort.sendPort));
    isolates.add(await Isolate.spawn(runSomething, receivePort.sendPort));
  
    receivePort.listen((data) {
      print('Data: $data');
    });
  }
  
  void runSomething(SendPort sendPort) async {
    var request = await HttpClient().getUrl(Uri.parse('https://swapi.co/api/people/1'));
    var response = await request.close();
  
    await for (var contents in response.transform(Utf8Decoder())) {
      sendPort.send(contents);
    }
  }
  
  void stop() {
    for (Isolate i in isolates) {
      if (i != null) {
        i.kill(priority: Isolate.immediate);
        i = null;
        print('Killed');
      }
    }
  }
  
  void main() async {
    await start();
  
    print('Press enter to exit');
    await stdin.first;
  
    stop();
    exit(0);
  }

Example 3

The third example is a combination of the first and second examples. We want to both send message to the created isolates and listen to the response of the isolates. The spawn method only has one parameter for message but it can be any type. So, we can create a custom class that contains both SendPort and the message, as exemplified below.

  import 'dart:convert';
  import 'dart:io';
  import 'dart:isolate';
  
  List<Isolate> isolates;
  
  class CustomObject {
    String message;
    SendPort sendPort;
  
    CustomObject(this.message, this.sendPort);
  }
  
  void start() async {
    isolates = new List();
    ReceivePort receivePort= ReceivePort();
  
    CustomObject object1 = new CustomObject('1', receivePort.sendPort);
    CustomObject object2 = new CustomObject('2', receivePort.sendPort);
    CustomObject object3 = new CustomObject('3', receivePort.sendPort);
  
    isolates.add(await Isolate.spawn(runSomething, object1));
    isolates.add(await Isolate.spawn(runSomething, object2));
    isolates.add(await Isolate.spawn(runSomething, object3));
  
    receivePort.listen((data) {
      print('Data: $data');
    });
  }
  
  void runSomething(CustomObject object) async {
    print('https://swapi.co/api/people/${object.message}');
    var request = await HttpClient().getUrl(Uri.parse('https://swapi.co/api/people/${object.message}'));
    var response = await request.close();
  
    await for (var contents in response.transform(Utf8Decoder())) {
      object.sendPort.send(contents);
    }
  }
  
  void stop() {
    for (Isolate i in isolates) {
      if (i != null) {
        i.kill(priority: Isolate.immediate);
        i = null;
        print('Killed');
      }
    }
  }
  
  void main() async {
    await start();
  
    print('Press enter to exit');
    await stdin.first;
  
    stop();
    exit(0);
  }