Isolate Implementation Explained: spawn() and run()

Aryan Bisht
CodeX
Published in
5 min readMay 14, 2024

In today’s article we will be implementing the isolate. But before implementing we should know what isolate are and how they different from asycn-wait and when should we use them. I have explained all these thing in detail in my last blog you can check it out here :-

https://medium.com/@AryanBeast/async-vs-isolate-in-flutter-parallelism-in-flutter-ae3954fb5d4c.

I am made the github repo for the same project you can also check code there here is the link :- https://github.com/AryanBisht-03/isolate_tutorial

Maybe you might have implemented isolates but the code has always been messy and tedious to write. In any case, this blog will guide you through the ups and downs of the Isolate current and better implementation. You might want to use the latest method or you might want to use the old method after all, all up to your use case.

First and foremost thing you should remember that isolate function should be outside of all the all class.

The above point it the one of most common mistake anyone make as it’s easy to forget and hard to find when occurred, so make sure of it.

A small recap of receive-port. A ReceivePort is like a two-way communication channel where one party can send messages and another can listen and respond. It enables bidirectional communication between different parts of a program or between different programs. We will be using this in our code

In this blog we will be adding first adding first 1000000 number which is computation heavy task if you try to do this simply or using async wait you will se UI lag in the app (Must try once to better understand). But here we will be using 2 different implementation of isolate. You can use what-ever suits your requirement.

run() method [Short-lived isolates]

The easiest way to move a process to an isolate in Flutter is with the Isolate.run method. This method spawns an isolate, passes a callback to the spawned isolate to start some computation, returns a value from the computation, and then shuts the isolate down when the computation is complete. This all happens concurrently with the main isolate, and doesn't block it.

The Isolate.run method requires a single argument, a callback function, that is run on the new isolate. This callback's function signature must have exactly one required, unnamed argument. When the computation completes, it returns the callback's value back to the main isolate, and exits the spawned isolate.

For example, we are doing sum of first 10000000 numbers are returning it. But make sure the function is outside all the classes.

Future<double> complexTask3() async {
const double iteration = 1000000000;
final double photos = await Isolate.run<double>(() {
double total = 0.0;
for (var i = 0; i < iteration; i++) {
total += i;
}
return total;
});
return photos;
}

spawn() method [longer-lived isolates]

Short-live isolates are convenient to use, but there is performance overhead required to spawn new isolates, and to copy objects from one isolate to another. If you’re doing the same computation using Isolate.run repeatedly, you might have better performance by creating isolates that don't exit immediately.

When you use the Isolate.run method, the new isolate immediately shuts down after it returns a single message to the main isolate. Sometimes, you'll need isolates that are long lived, and can pass multiple messages to each other over time. In Dart, you can accomplish this with the Isolate API and Ports. These long-lived isolates are colloquially known as background workers.

Long-lived isolates are useful when you have a specific process that either needs to be run repeatedly throughout the lifetime of your application, or if you have a process that runs over a period of time and needs to yield multiple return values to the main isolate.

Below code is the task we have to perform and don’t forget to put this function outside all the class so that Isolate can run it.

complexTask(SendPort sendPort) {
var total = 0.0;
for (var i = 0; i < 1000000000; i++) {
total += i;
}
sendPort.send(total);
}

Here we will be using the complexTask on button press.

ElevatedButton(
onPressed: () async {
final receivePort = ReceivePort();
await Isolate.spawn(complexTask, receivePort.sendPort);
receivePort.listen((total) {
debugPrint('Result 2: $total');
});
},
child: const Text('Task 1'),
),

So now, whenever you click on this button it will run the complexTask() without blocking any UI operation and whenever function is completed its task in another isolate will return the result using receivePort to main isolate as you were listening to it using listen().

Suppose, you want to pass some data to isolate then what. There is a way to that also.

In this example suppose we want to pass the end value of the for loop. Then code will look like something this.

....
ElevatedButton(
onPressed: () async {
final receivePort = ReceivePort();
await Isolate.spawn(complexTask3, (iteration: 1000000000, sendPort: receivePort.sendPort));
receivePort.listen((total) {
debugPrint('Result 2: $total');
});
},
child: const Text('Task 2'),
),
...

complexTask3(({int iteration, SendPort sendPort}) data) {
var total = 0.0;
for (var i = 0; i < data.iteration; i++) {
total += i;
}
data.sendPort.send(total);
}

Likewise you can send as many data variable as you want.

There are some limitation of isolation you can read about them here.

I hope you were able to under this topic properly. Follow me for more such articles.

More from Aryan Bisht

You can buy me coffee at :- https://www.buymeacoffee.com/aryanbisht

Level Up Coding And Development Journey

Thanks for being a part of our community! Before you go:

  • 👏 If you find this helpful Clap for the story and follow the author

(Your support is crucial for me to produce more articles like these, especially since monetization options are available in my country.)

--

--