Issue
Is it possible to encapsulate repeated send/responses to the same dart isolate within a single asynchronous function?
Background:
In order to design a convenient API, I would like to have a function asynchronously return the result generated by an isolate, e.g.
var ans = await askIsolate(isolateArgs);
This works fine if I directly use the response generated by a spawnUri call, e.g
Future<String> askIsolate(Map<String,dynamic> isolateArgs) {
ReceivePort response = new ReceivePort();
var uri = Uri.parse(ISOLATE_URI);
Future<Isolate> remote = Isolate.spawnUri(uri, [JSON.encode(isolateArgs)], response.sendPort);
return remote.then((i) => response.first)
.catchError((e) { print("Failed to spawn isolate"); })
.then((msg) => msg.toString());
}
The downside of the above approach, however, is that if I need to repeatedly call askIsolate, the isolate must be spawned each time.
I would instead like to communicate with a running isolate, which is certainly possible by having the isolate return a sendPort to the caller. But I believe since the 2013 Isolate refactoring , this requires the caller to listen to subsequent messages on the receivePort, making encapsulation within a single async function impossible.
Is there some mechanism to accomplish this that I’m missing?
Solution
A quick working example based on lrn’s comment above follows. The example initializes an isolate via spawnURI, and then communicates with the isolate by passing a new ReceivePort upon which a reply is expected. This allows the askIsolate to directly return a response from a running spawnURI isolate.
Note error handling has been omitted for clarity.
Isolate code:
import 'dart:isolate';
import 'dart:convert' show JSON;
main(List<String> initArgs, SendPort replyTo) async {
ReceivePort receivePort = new ReceivePort();
replyTo.send(receivePort.sendPort);
receivePort.listen((List<dynamic> callArgs) async {
SendPort thisResponsePort = callArgs.removeLast(); //last arg must be the offered sendport
thisResponsePort.send("Map values: " + JSON.decode(callArgs[0]).values.join(","));
});
}
Calling code:
import 'dart:async';
import 'dart:isolate';
import 'dart:convert';
const String ISOLATE_URI = "http://localhost/isolates/test_iso.dart";
SendPort isolateSendPort = null;
Future<SendPort> initIsolate(Uri uri) async {
ReceivePort response = new ReceivePort();
await Isolate.spawnUri(uri, [], response.sendPort, errorsAreFatal: true);
print("Isolate spawned from $ISOLATE_URI");
return await response.first;
}
Future<dynamic> askIsolate(Map<String,String> args) async {
if (isolateSendPort == null) {
print("ERROR: Isolate has not yet been spawned");
isolateSendPort = await initIsolate(Uri.parse(ISOLATE_URI)); //try again
}
//Send args to the isolate, along with a receiveport upon which we listen for first response
ReceivePort response = new ReceivePort();
isolateSendPort.send([JSON.encode(args), response.sendPort]);
return await response.first;
}
main() async {
isolateSendPort = await initIsolate(Uri.parse(ISOLATE_URI));
askIsolate({ 'foo':'bar', 'biz':'baz'}).then(print);
askIsolate({ 'zab':'zib', 'rab':'oof'}).then(print);
askIsolate({ 'One':'Thanks', 'Two':'lrn'}).then(print);
}
Output
Isolate spawned from http://localhost/isolates/test_iso.dart
Map values: bar,baz
Map values: zib,oof
Map values: Thanks,lrn
Answered By – ilikerobots
Answer Checked By – Clifford M. (FlutterFixes Volunteer)