A Dart Isolates example (Actors in Dart)

Dart Isolates give you a way to perform real multi-threading in Dart applications, the ability to truly run code on another thread. (For more information on that statement, see Dart futures are NOT run in a separate thread.)

When I first read that Isolates were like actors I was very interested in them, but then I was surprised to see that (IMHO) they are implemented in a more low-level/primitive way than something like the Akka Actors library. (The entrepreneur out there might see this as an opportunity to create an Akka-like port for Dart and Flutter.)

Therefore, to try to understand Dart Isolates I decided to try to create a relatively simple example. I’m not going to write too much about the following example, but as you can see, it’s heavily-documented, so if you want to understand the example, please read the comments as you read the source code.

A Dart Isolates example

Here’s the source code for my example:

import 'dart:async';
import 'dart:isolate';

main() async {

    // create a long-lived port for receiving messages
    var ourFirstReceivePort = new ReceivePort();

    // spawn an 'echo' actor, then 'await' for it to reply. 
    // 'echo' is the name of a function below;
    // see its source code to see how it works.
    await Isolate.spawn(echo, ourFirstReceivePort.sendPort);

    // the 'echo' isolate sends us its SendPort as its first message.
    // this lets us communicate with it. we’ll always use this port to 
    // send it messages.
    var echoPort = await ourFirstReceivePort.first;

    // if you try to use our first receive port, you’ll get this error:
    // “Bad state: Stream has already been listened to.”
    // so it seems like you always need a new port to communicate with 
    // an isolate (actor).
    var ourSecondReceivePort = ReceivePort();
    echoPort.send(['message 1', ourSecondReceivePort.sendPort]);
    var msg = await ourSecondReceivePort.first;
    print('main received "$msg"');

    // instead of 'await', use 'then' as a different way of receiving
    // a reply from 'echo' (handle it asynchronously, rather than 
    // waiting for the reply)
    var port3 = ReceivePort();
    echoPort.send(['message 2', port3.sendPort]);
    port3.first.then((msg) {
        print('main received "$msg"');
    });

    // use 'then' one more time
    var port4 = ReceivePort();
    echoPort.send(['port 4', port4.sendPort]);
    port4.first.then((msg) {
        print('main received "$msg"');
    });

    print('end of main');

}

// `echo` is an async function, and it works a little like
// the equivalent of an Akka actor.
echo(SendPort sendPort) async {

    // open our receive port. this is like turning on
    // our cellphone.
    var ourReceivePort = ReceivePort();

    // tell whoever created us what port they can reach us on
    // (like giving them our phone number)
    sendPort.send(ourReceivePort.sendPort);

    // listen for text messages that are sent to us,
    // and respond to them with this algorithm
    await for (var msg in ourReceivePort) {
        var data = msg[0];                // the 1st element we receive should be their message
        print('echo received "$data"');
        SendPort replyToPort = msg[1];    // the 2nd element should be their port

        // add a little delay to simulate some work being done
        Future.delayed(const Duration(milliseconds: 100), () {
            // send a message back to the caller on their port,
            // like calling them back after they left us a message
            // (or if you prefer, they sent us a text message, and
            // now we’re texting them a reply)
            replyToPort.send('echo said: ' + data);
        });

        // you can close the ReceivePort if you want
        //if (data == "bye") ourReceivePort.close();
    }
}

Program output

The output from this Dart isolates example looks like this:

echo received "message 1"
main received "echo said: message 1"
echo received "message 2"
end of main
echo received "port 4"
main received "echo said: message 2"
main received "echo said: port 4"

As the output shows, when I use await with the first message, the initial output is printed in synchronous order. But once I start using then with “message 2” and “port 4”, those replies are received asynchronously, after “end of main” is printed.

Personally, I prefer the then approach because it reminds me of what I’m comfortable with when using Scala Futures, but the Dart/Flutter documentation seems to prefer using await, so I thought I’d show both examples.

Many thanks to ...

I don’t know if I would have ever gotten very far with Dart Isolates without this example, so many thanks to jpryan.me:

My example is heavily based on that example, except I couldn’t initially understand the need for the sendReceive function, so I removed that and handled things manually.