【问题标题】:encapsulate repeated send/responses to the same Dart isolate within a single asyncronous function在单个异步函数中封装对同一个 Dart 隔离的重复发送/响应
【发布时间】:2016-04-08 12:56:36
【问题描述】:

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

背景:

为了设计一个方便的API,我想让一个函数异步返回一个isolate生成的结果,例如

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 上监听后续消息,从而无法在单个异步函数中进行封装。

是否有一些我缺少的机制来完成这个?

【问题讨论】:

  • 我有段时间玩孤立的。 pub.dartlang.org/packages/isolate 应该为隔离提供一个很好的 API。我认为值得仔细研究一下。
  • IsolateRunner 中的package:isolate 用于多次调用衍生隔离中的函数。我认为它可以解决这个问题:var runner = await IsolateRunner.spawn(); for (var arg in something) }{ ... await runner.run(queryFunction, arg); ... } await runner.close();
  • 另一种选择是让服务隔离运行,但不是让它每次都在同一个端口上返回结果,而是每个请求都可以发送自己的SendPort。然后每个请求都可以创建一个ResponsePort 并返回first getter 作为响应:Future askIsolate(isolateArgs) { var p = new ReceivePort(); runningIsolatePort.send([isolateArgs, p.sendPort]); return p.first; }
  • 我没有成功让 IsolateRunner 在 spawnURI 情况下正常工作。我需要能够在外部 dart 脚本上调用和执行已知方法。我知道您可以使用 IsolateRunner 手动包装 Isolate,但我没有看到如何利用 IsolateRunner.run 来执行外部方法。但是,lrn 的第二个建议似乎效果很好。我将完成一些测试并发布一个完整的示例

标签: dart dart-isolates


【解决方案1】:

答案取决于你打算如何使用隔离

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

  • 是否要一次向隔离发送多个(但有限的)输入,期望异步接收答案,然后关闭隔离?

我猜是后者,你的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

【讨论】:

  • 其实你两颗子弹中的前者是我想要的。感谢您对 then(...).catchError(...) 问题的关注!
  • 你想实现一个函数,向一个isolate发送一个输入,并返回一个在isolate发回结果时完成的future,你可以重复调用它吗?
【解决方案2】:

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

如果你不想这样,还有其他更原始的选项

异步函数可以等待期货或流,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.
}

【讨论】:

    【解决方案3】:

    下面是一个基于 lrn 上述评论的快速工作示例。该示例通过 spawnURI 初始化一个隔离,然后通过传递一个新的 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
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-03-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多