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

1

5

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?

Zest answered 20/4, 2021 at 15:23 Comment(0)
J
6

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.

Jany answered 20/4, 2021 at 23:29 Comment(4)
Thank you for your answer! Very clear. However, regarding the compute method I have to say that, when you mean that it removes the need to use ports, do you mean it can't use them? Because it is actually possible to use compute with ports.Zest
@IvánYoed No, I meant exactly as I said, it removes the need. Since compute returns a Future that completes upon the computation passed to it returning, there is no need to use ports to determine when the isolate is finished. You could use ports with compute if you wanted, but I would personally just go for a normal isolate at that point.Jany
Great. So everything is clear now. I'm accepting your answer. Thanks again.Zest
Just as a complement to this answer, as it can be inferred, your previous example behaves the same when using the compute method instead of Isolate.spawn.Zest

© 2022 - 2024 — McMap. All rights reserved.