I am kinda OK with my current remote proxy pattern implementation in Dart. Sorta.
Actually, the proxy itself is decent, thanks in large part to the async / await syntax introduced the other night:
main() async { // Spawn remote isolate worker here... ProxyCar car = new ProxyCar(receiveStream, s); await car.drive(); print("Car is ${car.state}"); await car.stop(); print("Car is ${await car.state}"); }Futures and promises are nice constructs, but they sure can get noisy. Dart uses
await
inside of an async
function as syntactic sugar. That code looks so nice that the code responsible for spawning the isolate worker is bugging me:
main() async { var r = new ReceivePort(); var receiveStream = r.asBroadcastStream(); await Isolate.spawn(other, r.sendPort); SendPort s = await receiveStream.first; ProxyCar car = new ProxyCar(receiveStream, s); // ... }I think I would prefer my client code to look something like:
ProxyCar car = new ProxyCar();
await Isolate.spawn(other, car.sendPort);
await car.ready;
// ...
I initially try to jam all of the isolate communication code directly into the ProxyCar
. As most would expect, that made for a messy ProxyCar
. So instead, I define a Talker
class to establish a ReceivePort
in the current isolate, listen on that same ReceivePort
for a SendPort
sent by the spawned isolate, and a method for sending messages on that SendPort
:class Talker { SendPort _s; ReceivePort _r; Stream _inStream; Future ready; Talker() { _r = new ReceivePort(); _inStream = _r.asBroadcastStream(); ready = _inStream.first.then((message){ _s = message; }); } // For others to talk to us SendPort get sendPort => _r.sendPort; Future send(message) { _s.send(message); return _inStream.first; } }I find myself getting confused by the
SendPort
from the other isolate and the SendPort
that this has to expose for the other isolate to communicate back. As a first pass, I make the SendPort
from the other isolate private. It is only used internally as is the ReceivePort
for communicating back to Talker
. I like that except that the SendPort
associated with the private ReceivePort
is publicly available. So I get confused. I really want to find better names for these beasties, but I have to admit that "send port" and "receive port" capture the intent as well as anything. Oh well.With
Talker
, I can redefine ProxyCar
as:class ProxyCar implements AsyncAuto { String state = "???"; Talker _t; ProxyCar() { _t = new Talker(); } Future drive() => _send(#drive); Future stop() => _send(#stop); SendPort get sendPort => _t.sendPort; Future get ready => _t.ready; Future _send(message) => _t. send(message). then((response){ print("[ProxyCar] $response"); state = response; }); }That is still a little heavy on the communication side, but I can live with it as all of it delegates to
Talker
or to the proxied methods (drive()
, stop()
, etc.). With that, my original goal is met. The client code becomes simply:
main() async { ProxyCar car = new ProxyCar(); await Isolate.spawn(other, car.sendPort); await car.ready; await car.drive(); print("Car is ${car.state}"); print("--"); await car.stop(); print("Car is ${await car.state}"); }While I like some of this approach, it is proving difficult to explain. I may have to revisit the example or the approach before it is ready for inclusion in Design Patterns in Dart.
Day #64
No comments:
Post a Comment