Understanding Isolates

Let’s say we don’t want to use compute function and want to get the same result using Isolates only.

Isolates rely on message passing to send and receive information. They do not share information and memory, which helps in getting rid of dead locks and anomaly behavior.

We send these messages on what dart calls as ports

Each isolate have two ports.

Send Port — Sends message to isolate. Receive Port — Receive message from isolate.

We want to setup our code keeping these points in mind.

There will be 2 isolates. Lets call one main flutter isolate, and other prime isolate. Main isolate must have both send and receive port of prime isolate. Send port of prime isolate will be used to send the parameter n. Receive port of prime isolate will be used to get resultant prime number. Prime isolate should have both send and receive port of main isolate. Receive port of main isolate will be used to receive the parameter n. Send port of main isolate will be used to send the resultant prime number.

At first, this seems like a lot, but all we are doing is creating a pipe between the two isolates using the ports.

First, let us create a receive port for main isolate.

ReceivePort receivePort = ReceivePort();

Each receive port also have a send port. Each message which is send on that port is received by that particular receive port. We need to pass this send port to our prime isolate, which we will spawn in just a moment.

await Isolate.spawn(getnthPrime, receivePort.sendPort);

We, now also need to update the definition of getnthPrime function. As isolates can communicate using messages only, we no longer need the value of n as a parameter.

static getnthPrime(SendPort sendPort) async {

At this point, we have set up the send port of the main isolates to the prime isolate. Using this port, we will now send the send port of the prime isolate back to main isolate. This is like a handshaking.

static getnthPrime(SendPort sendPort) async {

// Port for receiving message from main isolate.

// We will receive the value of n using this port.

ReceivePort receivePort = ReceivePort();

// Sending the send Port of isolate to receive port of main isolate

sendPort.send(receivePort.sendPort);

We will receive this port in the main isolate.

void _getPrime() async {

// Port where we will receive our answer to nth prime.

// From isolate to main isolate.

ReceivePort receivePort = ReceivePort();

await Isolate.spawn(getnthPrime, receivePort.sendPort);



// Send port for the prime number isolate. We will send parameter n

// using this port.

SendPort sendPort = await receivePort.first;

Now, both the isolates have send port of each other. We will now setup a middle ware function to send messages to the isolate.

Future sendReceive(SendPort send, message) {

ReceivePort receivePort = ReceivePort();

send.send([message, receivePort.sendPort]);

return receivePort.first;

}

This function sends the message and a port to receive the answer. We can simply use this function to read the answer.

int ans = await sendReceive(sendPort, 100);

The 3 function looks like this.

/// Returns the nth prime number.

static getnthPrime(SendPort sendPort) async {

// Port for receiving message from main isolate.

// We will receive the value of n using this port.

ReceivePort receivePort = ReceivePort();

// Sending the send Port of isolate to receive port of main isolate

sendPort.send(receivePort.sendPort);

var msg = await receivePort.first;



int n = msg[0];

SendPort replyPort = msg[1];

int currentPrimeCount = 0;

int candidate = 1;

while (currentPrimeCount < n) {

++candidate;

if (isPrime(candidate)) {

++currentPrimeCount;

}

}

replyPort.send(candidate);



}



void _getPrime() async {

// Port where we will receive our answer to nth prime.

// From isolate to main isolate.

ReceivePort receivePort = ReceivePort();

await Isolate.spawn(getnthPrime, receivePort.sendPort);



// Send port for the prime number isolate. We will send parameter n

// using this port.

SendPort sendPort = await receivePort.first;

int ans = await sendReceive(sendPort, 10000);

setState(() {

prime = ans;

});

}



Future sendReceive(SendPort send, message) {

ReceivePort receivePort = ReceivePort();

send.send([message, receivePort.sendPort]);

return receivePort.first;

}

Now, if you run the app, you would find that UI is buttery smooth. You do not need to set up all these isolates yourself. Notice how we achieved the same result using the compute function provided by flutter.

This article was inspired by the discussion on a issue here. You can find the complete code at git repo here.