In this post we will explore how to create an Isolate in Dart. Think of an Isolate as a worker process you can create to execute code but does not share memory with the process that spawned it - as the name implies it is isolated and has its own memory heap. So how do you communicate with an Isolate? This is where messages come into play. Isolates have both a sending and receiving port from which you can send and receive information.

UPDATE: 04/16/18:

I have received a few questions about how to use isolates in dart. So I created a public gist here for those who are interested: Using Isolates in Flutter

Why use an Isolate?

On the surface an Isolate might just seem like another way of executing a task in an asynchronous manner (see my Dart tutorial on async here for more on that) but there is a key difference. Async operations in Dart operate on the same thread as the user interface whereas an Isolate gets its own thread. Therefore, if you want to execute a process that is fairly intense and you want to make sure you keep your app responsive and snappy in the eyes of the user, then you should consider using Isolates.

How to create and start an Isolate

To create an isolate, you may use the spawn method. At a minimum, you must give the spawn method an 'entry point' method with a single parameter. It is very typical that this parameter represents the port which the isolate should use to send back notification messages. Here is the simplest of examples:

Isolate isolate; void start() async { ReceivePort receivePort= ReceivePort(); //port for this main isolate to receive messages. isolate = await Isolate.spawn(runTimer, receivePort.sendPort); receivePort.listen((data) { stdout.write('RECEIVE: ' + data + ', '); }); } void runTimer(SendPort sendPort) { int counter = 0; Timer.periodic(new Duration(seconds: 1), (Timer t) { counter++; String msg = 'notification ' + counter.toString(); stdout.write('SEND: ' + msg + ' - '); sendPort.send(msg); }); }

In the example above, we have our start() method which creates a port and spawns an isolate. Our start method is marked as async so that we can await the response from the spawning of the isolate and so that we can store a reference to the new isolate (this becomes important later when we cover how to kill a running isolate). Note that we are giving the spawn call two things - a callback function to execute - in this case runTimer(), and a port (sendPort) which the callback can use to send messages back to the caller. Ports are how you communicate with your isolate and although we set this up to be one way communication (from the isolate back to the caller) it can be two-way. The last thing we do in the start() method is to begin listening on the receivePort for messages from the isolate. Whenever we receive a message, we write it out to the console.

Next, take a look at the runTimer method. This method starts up a periodic Timer that fires every second in order to update a counter and send out a notification message via the port which it received when the isolate was spawned. If you were to run this code as a console application, you would see the following output:

SEND: notification 1 - RECEIVE: notification 1, SEND: notification 2 - RECEIVE: notification 2

Stopping an Isolate and cleanup

The isolate that we spawned above could go on for an indefinite amount of time. This is because we created a timer that wakes up every second with no end in sight. But what if we wanted to stop it? In order to stop an isolate, you would use the kill method.

void stop() { if (isolate != null) { stdout.writeln('killing isolate'); isolate.kill(priority: Isolate.immediate); isolate = null; } }

The above code kills the running isolate and sets the reference to null. The priority of Isolate.immediate will cleanly shut down the isolate at the nearest opportunity.

Putting it all together

Let's create a main entry point to call our start() method and also our new stop() method. Here is the complete working example.

import 'dart:io'; import 'dart:async'; import 'dart:isolate'; Isolate isolate; void start() async { ReceivePort receivePort= ReceivePort(); //port for this main isolate to receive messages. isolate = await Isolate.spawn(runTimer, receivePort.sendPort); receivePort.listen((data) { stdout.write('RECEIVE: ' + data + ', '); }); } void runTimer(SendPort sendPort) { int counter = 0; Timer.periodic(new Duration(seconds: 1), (Timer t) { counter++; String msg = 'notification ' + counter.toString(); stdout.write('SEND: ' + msg + ' - '); sendPort.send(msg); }); } void stop() { if (isolate != null) { stdout.writeln('killing isolate'); isolate.kill(priority: Isolate.immediate); isolate = null; } } void main() async { stdout.writeln('spawning isolate...'); await start(); stdout.writeln('press enter key to quit...'); await stdin.first; stop(); stdout.writeln('goodbye!'); exit(0); }

To execute the above program, you simply need to execute the command 'dart main.dart' (assuming main.dart is the name of the file you saved the above example as...). Make sure of course dart.exe is found on your path first! You should see something similar to the below output when running the program:

spawning isolate... press enter key to quit... SEND: notification 1 - RECEIVE: notification 1, SEND: notification 2 - RECEIVE: notification 2, SEND: notification 3 - RECEIVE: notification 3, killing isolate goodbye!

Summary

In this article we covered the basics of spawning an isolate, why you would consider using isolates, and how to stop them. It would be straight forward to modify the above example to fit your needs by replacing the runTimer method with whatever code you wish to execute within your isolate. Cheers!