Description
I've been trying to use Dart Isolates for the past few days and it's been a total nightmare. Most of the documentation and tutorials ask to use compute
from Flutter. But compute
returns the result, not the context, so it is not cancellable. Then, the function that you pass must be in the root level, not in the function level, which makes the error message unclear:
[ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: Invalid argument(s): Illegal argument in isolate message: (object extends NativeWrapper - Library:'dart:ui' Class: Path)
I heard there is also a limitation where you can't return every type, like only primitive types work. And that it copies the values when returning. So, making a simple Isolate from hand is really really hard, there are many edge scenarios, and sometimes it is slower. It is done in a way no one should use, except most people could benefit from parallelism.
I understand how it is thread-safe, memory-safe, and these things are great. But it is implemented in such a non-intuitive way that no uses it in normal situations.
I wish Dart could learn some lessons from coroutines or goroutines and make "async"/multi-thread/parallelism easy to use for anyone. A few months ago, there was describeEnum
in Flutter, now Dart takes care of it. Now, I wish there were some care for compute
, so hopefully one day it can be deprecated in Flutter with a better solution.
How it is right now (got this code somewhere on stack overflow):
Future<Person> fetchUser() async {
ReceivePort port = ReceivePort();
String userData = await Api.getUser();
final isolate = await Isolate.spawn<List<dynamic>>(
deserializePerson, [port.sendPort, userData]);
final person = await port.first;
isolate.kill(priority: Isolate.immediate);
return person;
}
void deserializePerson(List<dynamic> values) {
SendPort sendPort = values[0];
String data = values[1];
Map<String, dynamic> dataMap = jsonDecode(data);
sendPort.send(Person(dataMap["name"]));
}
How it could be:
Future<Person> fetchUser() async {
final person = await launch(Thread.io, () => deserializePerson(userData));
}
The scoping mechanism with launch and useContext in Kotlin is perfect to me. It is not memory safe, but the scope helps it be easy to use and intuitive.