3

So I basically have a simple class with a update() method. But because that update() method makes some math, I wanted to use compute() to make it run in another Isolate. The plan was to run the update() method in the Isolate and return the updated object like this:

compute(updateAsset, asset).then((value) => asset = value);

Asset updateAsset(Asset asset) {
  asset.update();
  return asset;
}

But then I get this error:

ArgumentError (Invalid argument(s): Illegal argument in isolate message : (object extends NativeWrapper - Library:'dart:ui' Class: Path))

Is there any possible way to send an object to an Isolate or do I have to send every single Value of that Asset as an Integer, create a new Object and return that?

dev-aentgs
  • 1,218
  • 2
  • 9
  • 17
Quasi
  • 576
  • 4
  • 13

2 Answers2

2

The accepted answer is out of date now, there appears to be a lot more felexibility in what you can send in Flutter 3.7. The cookbook example says:

Isolates communicate by passing messages back and forth. These messages can be primitive values, such as null, num, bool, double, or String, or simple objects such as the List in this example.

You might experience errors if you try to pass more complex objects, such as a Future or http.Response between isolates.

The example code shows this:

Future<List<Photo>> fetchPhotos(http.Client client) async {
  final response = await client
      .get(Uri.parse('https://jsonplaceholder.typicode.com/photos'));

  // Use the compute function to run parsePhotos in a separate isolate.
  return compute(parsePhotos, response.body);
}

// A function that converts a response body into a List<Photo>.
List<Photo> parsePhotos(String responseBody) {
  final parsed = jsonDecode(responseBody).cast<Map<String, dynamic>>();

  return parsed.map<Photo>((json) => Photo.fromJson(json)).toList();
}

class Photo {
  final int albumId;
  final int id;
  final String title;
  final String url;
  final String thumbnailUrl;

  const Photo({
    required this.albumId,
    required this.id,
    required this.title,
    required this.url,
    required this.thumbnailUrl,
  });

  factory Photo.fromJson(Map<String, dynamic> json) {
    return Photo(
      albumId: json['albumId'] as int,
      id: json['id'] as int,
      title: json['title'] as String,
      url: json['url'] as String,
      thumbnailUrl: json['thumbnailUrl'] as String,
    );
  }
}

It's a little unclear what they mean by "simple objects such as the List" - what is a simple object?

The docs shed a bit more light on it:

The transitive object graph of message can contain the following objects:

Null
bool
int
double
String
List, Map or Set (whose elements are any of these)
TransferableTypedData
SendPort
Capability
Type representing one of these types, Object, dynamic, void or Never

If the sender and receiver isolate share the same code (e.g. isolates created via Isolate.spawn), the transitive object graph of message can contain any object, with the following exceptions:

Objects with native resources (subclasses of e.g. NativeFieldWrapperClass1). > A Socket object for example referrs internally to objects that have native resources attached and can therefore not be sent.

ReceivePort
DynamicLibrary
Finalizable
Finalizer
NativeFinalizer
Pointer
UserTag
MirrorReference

Apart from those exceptions any object can be sent. Objects that are identified as immutable (e.g. strings) will be shared whereas all other objects will be copied.

The send happens immediately and may have a linear time cost to copy the transitive object graph. The send itself doesn't block (i.e. doesn't wait until the receiver has received the message). The corresponding receive port can receive the message as soon as its isolate's event loop is ready to deliver it, independently of what the sending isolate is doing.

James Allen
  • 6,406
  • 8
  • 50
  • 83
1

According to the docs:

The content of message can be: primitive values (null, num, bool, double, String), instances of SendPort, and lists and maps whose elements are any of these. List and maps are also allowed to be cyclic.

So I see a 2 options that you can use.

  1. You can send every value as an integer or other primitive in a Map or List if it's possible for your object to be deconstructed like that.
  2. If that method is too difficult for any reason, you can instead convert you object to a primitive type, the easiest being String with JSON encoding. You can encode your object with the jsonEncode function and send the String that it returns over to your isolate where you would then decode it back to your object if necessary.
Christopher Moore
  • 15,626
  • 10
  • 42
  • 52
  • Thx a lot. Another question. In terms of runtime, would it be more efficient to call compute() with a list of items or call compute() for every item in that list. And also doesn't encoding and decoding also delay the process alot? update() is roughly called every frame so yeah thx alot. – Quasi Jul 28 '20 at 14:19
  • @evilevidenz Calling `compute` with a list of items is probably more efficient. Encoding and decoding certainly adds some overhead. So manually mapping your object to primitive values would be more efficient, but may not be practical for your application. – Christopher Moore Jul 28 '20 at 14:24