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.