encapsulate repeated send/responses to the same Dart isolate within a single asyncronous function

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)

Leave a Reply

Your email address will not be published.