Flutter & Dart: What is the correct way to manage obtained data from a computation made in an Isolate?

Issue

When doing expensive computations in Dart it is highly recommended to start up additional isolates. I know that, since isolates don’t share any state, if you want to create communication between them, there is the possibility to pass a message from one to another by using SendPort and ReceivePort. However, when doing a computation in another Isolate, within the process of this new created isolate one could actually initialize an already created variable, for instance a variable of a Singleton, right?

So my questions are: Is it okay to proceed like this? What is the correct way to manage obtained data from a computation made in an isolate and why? Should we always only work with messages when doing computations within new isolates?

Solution

No, you cannot initialize a variable of one isolate from a different isolate. They don’t have shared memory as you alluded to. The only method of communication between isolates is through ports.

Even though the analyzer may allow to you to modify a variable in one isolate from another, you won’t get the behavior you expect. Isolates can be thought of as entirely different processes.

I’ve made an example to show this behavior:

import 'dart:isolate';

Future<void> main(List<String> arguments) async {
  final single = Singleton();
  print(single.variable);

  single.variable = 1;
  print(single.variable);

  final port = ReceivePort();
  final isolate = await Isolate.spawn(isolateWork, port.sendPort);
  await for(dynamic message in port) {
    if(message == 'done') {
      isolate.kill();
      break;
    }
  }
}

void isolateWork(SendPort port) {
  final single = Singleton();
  print('In isolate: ${single.variable}');
  port.send('done');
}

class Singleton {
  int variable = 0;

  static final Singleton _singleton = Singleton._internal();

  factory Singleton() {
    return _singleton;
  }

  Singleton._internal();
}

This is a simple program that attempts to use a singleton to modify a variable in one isolate from a spawned one. If there were shared memory, one might expect the following output as I first obtain the singleton, print the default variable value of 0, change variable to 1, and then print variable in the isolate:

0
1
In isolate: 1

However, since these isolates are completely separate processes, the spawned isolate has its own instance of the singleton, leading to the following output:

0
1
In isolate: 0

0 and 1 are printed as expected in the main isolate, but the spawned one has its own memory and uses a separate singleton object, so it prints 0 even though it’s 1 in the main isolate.


As you can see in my example above, I did use ports so that my program could detect when the isolate was finished and gracefully kill the process. Passing messages with ports are the proper and only method of passing data between with isolates.


You tagged this question as , so I’m guessing you’re using Flutter. If you’re doing a single, large computation with a single output, using Flutter’s compute method is probably the easiest way to use isolates as it removes the need for you to work with ports. However, it can be limiting and it’s often useful to use a full isolate implementation like I did above.

Answered By – Christopher Moore

Answer Checked By – Terry (FlutterFixes Volunteer)

Leave a Reply

Your email address will not be published.