在单个异步函数

时间:2016-04-08 12:56:36

标签: dart dart-isolates

是否可以在单个异步函数中封装重复的发送/响应到同一个dart隔离区?

背景:

为了设计一个方便的API,我希望有一个函数异步返回隔离生成的结果,例如

var ans = await askIsolate(isolateArgs);

如果我直接使用spawnUri调用生成的响应,这可以正常工作,例如

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());
}

然而,上述方法的缺点是,如果我需要重复调​​用askIsolate,则每次都必须生成隔离。

我希望与正在运行的隔离区进行通信,这当然可以通过让隔离区将sendPort返回给调用方来实现。但我相信自2013 Isolate refactoring以来,这需要调用者监听receivePort上的后续消息,从而无法在单个异步函数中进行封装。

是否有某种机制可以实现这一目标,而我却错过了?

3 个答案:

答案 0 :(得分:2)

答案取决于您打算如何使用分离

  • 您打算让它无限期地运行,发送输入并期望异步接收答案吗?

  • 你想立即发送隔离多个(但是有限的)输入,期望异步接收答案,然后关闭隔离?

我猜测后者,你的askIsolate()函数需要立即返回Future,而不是在收到所有答案时完成。

await for循环可用于侦听流并消耗事件直到它关闭。

我不熟悉隔离物,所以我希望这没关系,我还没有测试过它。我假设隔离终止并且响应关闭。

String askIsolate(Map<String,dynamic> isolateArgs) async {

  ReceivePort response = new ReceivePort();
  var uri = Uri.parse(ISOLATE_URI);

  Isolate.spawnUri(uri, [JSON.encode(isolateArgs)], response.sendPort)
    .catchError((e)) {
     throw ...;
   });

  List<String> answers = new List<String>;

  await for(var answer in response) {
    out.add(answer.toString());
  }

  return answers;
}

注意:

  • response是您正在收听答案的信息流。它是在产生隔离之前创建的所以你不需要(也可能不应该)在听之前等待隔离未来完成。

  • 我做了askIsolate()异步,因为这样可以很容易地立即返回一个在函数返回时完成的未来 - 没有用.then(...)链条进行的那些繁琐的讨论,我个人觉得令人困惑,难以阅读。

顺便说一句,你原来的then(...).catchError(...)样式代码会更好地写成:

  Isolate.spawnUri(uri, [JSON.encode(isolateArgs)], response.sendPort)
    .catchError((e) { ... });

   return response.first)
     .then((msg) => msg.toString());

我相信在创建隔离专区之后延迟将一个catchError处理程序附加到该行可能允许将来在处理程序到位之前完成错误

请参阅:https://www.dartlang.org/articles/futures-and-error-handling/#potential-problem-failing-to-register-error-handlers-early

答案 1 :(得分:2)

我还建议在IsolateRunner中查看package:isolate,它旨在解决这样的问题 - 在同一个隔离中调用一个函数几次,而不是在创建隔离时调用一次。

如果您不想要,那么还有其他更原始的选项

异步函数可以等待期货或流,而ReceivePort是一个流。 对于快速入侵,您可以在响应流上使用await for执行某些操作,但它不会非常方便。

ReceivePort中的StreamQueue包裹在package:async中是更好的选择。这允许您将个别事件转换为期货。类似的东西:

myFunc() async {  
  var responses = new ReceivePort();
  var queue = new StreamQueue(responses);
  // queryFunction sends its own SendPort on the port you pass to it.
  var isolate = await isolate.spawn(queryFunction, [], responses.sendPort);
  var queryPort = await queue.next();
  for (var something in somethingToDo) {
    queryPort.send(something);
    var response = await queue.next();
    doSomethingWithIt(response);
  }
  queryPort.send("shutdown command");  
  // or isolate.kill(), but it's better to shut down cleanly.
  responses.close();  // Don't forget to close the receive port.
}

答案 2 :(得分:1)

基于以上lrn评论的快速工作示例如下。该示例通过spawnURI初始化隔离,然后通过传递一个新的ReceivePort与隔离进行通信,在该ReceivePort上需要回复。这允许askIsolate直接从正在运行的spawnURI隔离区返回响应。

为清楚起见,省略了错误处理。

隔离代码:

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(","));
  });
}

致电代码:

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);
}  

<强>输出

Isolate spawned from http://localhost/isolates/test_iso.dart
Map values: bar,baz
Map values: zib,oof
Map values: Thanks,lrn