Dart Isolates As Workers
Asked Answered
M

4

6

Edited to make the question more clear.

I am trying to work with Isolates (or Web Workers) in Dart. The only ways I can find to communicate between the main and isolate threads are send and call & then from the main thread. But that's a nice way for the main thread to pass some data to the isolate.

What's if I want the isolate to be the one who generates information? Like a game engine that does all the physics in a worker and then sends an updated world information to the main thread? In JavaScript you can send data at any time. Is there an efficient way in Dart? Or do I still have to wait for the main thread to call me and then pass it to it?

P.S. I wonder, does call & then block the thread until reply is done or not?

Mireillemireles answered 24/4, 2012 at 16:44 Comment(0)
T
3

WARNING: this code only works on very old versions of Dart. It does not work on Dart 1.0 or later.

As you mention to post messages to a isolate you need to have a handle on it's sendport.

#import('dart:isolate');

main() {
  SendPort sendPort = spawnFunction(doWork);
  sendPort.call("hey 1").then((String res) => print("result was: [$res]"));
  sendPort.call("hey 2").then((String res) => print("result was: [$res]"));
}

doWork() {
  port.receive((msg, reply) {
    msg = "msg $msg";
    reply.send(msg);
  });
}

however since the Dart main thread is itself an isolate you can send data to it by using the global port function:

#import('dart:isolate');
#import('dart:io');

main() {
   port.receive((data, reply) {
       // in here you can access objects created in the main thread
       print("handle [${data['text']}] for index ${data['index']}");
   });

   SendPort workPort = spawnFunction(doWork);
   workPort.send("msg", port.toSendPort());
}

doWork() {
   port.receive((msg, reply) {
      int i = 0;
      new Timer.repeating(1000, (Timer timer) {
         i++;
         var data = {
            "text": "$msg $i",
            "index": i
         };
         print("sending $data");
         reply.send(data);
      });
   });
}

Note there are certain limits about what can be send back and forth between isolates and also currently isolates act differently in JS and on the VM. The current limitations are well described here.

Thickhead answered 24/4, 2012 at 21:22 Comment(5)
But what's if I want my isolate to generate/update data? Like a game engine. It's supposedly runs all the calculations and then passes updated statuses of the object in-game. Is there an efficient mechanism for that or do I have to build it on top of Isolates?Mireillemireles
@Pius you cannot send references into a isolate, all data worked upon are copied as described here api.dartlang.org/dart_isolate/SendPort.html#sendThickhead
I am not talking about references at all. I am talking about data. Have you ever worker with Web Workers in JavaScript? You can send data from the worker at any time. And as much, as you want. Worker could work and send data without even listening to the main thread while the main thread could simply be receiving data using a callback function. I am talking about self.postMessage function equivalent in isolate.Mireillemireles
@Pius I think I see your point. The Dart main thread is itself an isolate which you can access via the global "port" function so your isolate can just get a handle on this - I have updated the code.Thickhead
Timer is now in dart:isolate , not in dart:io.Crawly
C
5

As of Dart 1.0, you can use isolates like this:

import 'dart:isolate';
import 'dart:async';

void doStuff(SendPort sendPort) {
  print('hi from inside isolate');
  ReceivePort receivePort = new ReceivePort();
  sendPort.send(receivePort.sendPort);

  receivePort.listen((msg) {
    print('Received in isolate: [$msg]');
    sendPort.send('ECHO: $msg');
  });

}

void main() {
  SendPort sendPort;

  ReceivePort receive = new ReceivePort();
  receive.listen((msg) {
    if (sendPort == null) {
      sendPort = msg;
    } else {
      print('From isolate: $msg');
    }
  });

  int counter = 0;

  Isolate.spawn(doStuff, receive.sendPort).then((isolate) {
    new Timer.periodic(const Duration(seconds:1), (t) {
      sendPort.send('Count is ${counter++}');
    });
  });
}
Crawly answered 20/1, 2014 at 3:57 Comment(0)
T
3

WARNING: this code only works on very old versions of Dart. It does not work on Dart 1.0 or later.

As you mention to post messages to a isolate you need to have a handle on it's sendport.

#import('dart:isolate');

main() {
  SendPort sendPort = spawnFunction(doWork);
  sendPort.call("hey 1").then((String res) => print("result was: [$res]"));
  sendPort.call("hey 2").then((String res) => print("result was: [$res]"));
}

doWork() {
  port.receive((msg, reply) {
    msg = "msg $msg";
    reply.send(msg);
  });
}

however since the Dart main thread is itself an isolate you can send data to it by using the global port function:

#import('dart:isolate');
#import('dart:io');

main() {
   port.receive((data, reply) {
       // in here you can access objects created in the main thread
       print("handle [${data['text']}] for index ${data['index']}");
   });

   SendPort workPort = spawnFunction(doWork);
   workPort.send("msg", port.toSendPort());
}

doWork() {
   port.receive((msg, reply) {
      int i = 0;
      new Timer.repeating(1000, (Timer timer) {
         i++;
         var data = {
            "text": "$msg $i",
            "index": i
         };
         print("sending $data");
         reply.send(data);
      });
   });
}

Note there are certain limits about what can be send back and forth between isolates and also currently isolates act differently in JS and on the VM. The current limitations are well described here.

Thickhead answered 24/4, 2012 at 21:22 Comment(5)
But what's if I want my isolate to generate/update data? Like a game engine. It's supposedly runs all the calculations and then passes updated statuses of the object in-game. Is there an efficient mechanism for that or do I have to build it on top of Isolates?Mireillemireles
@Pius you cannot send references into a isolate, all data worked upon are copied as described here api.dartlang.org/dart_isolate/SendPort.html#sendThickhead
I am not talking about references at all. I am talking about data. Have you ever worker with Web Workers in JavaScript? You can send data from the worker at any time. And as much, as you want. Worker could work and send data without even listening to the main thread while the main thread could simply be receiving data using a callback function. I am talking about self.postMessage function equivalent in isolate.Mireillemireles
@Pius I think I see your point. The Dart main thread is itself an isolate which you can access via the global "port" function so your isolate can just get a handle on this - I have updated the code.Thickhead
Timer is now in dart:isolate , not in dart:io.Crawly
C
1

Here is an example where parent creates two isolates and then two isolates also talk to each other along with the parent process.

Parent Code:

import 'dart:isolate';
import 'dart:html';
import 'dart:async';

main() {
  querySelector('#output').text = 'Your Dart app is running.';
  int counter = 0;

  // Parent - Child 1
  SendPort csendPort1;
  ReceivePort receivePort1 = new ReceivePort();
  // Parent - Child 2
  SendPort csendPort2;
  ReceivePort receivePort2 = new ReceivePort();
  // Child1 - Child2
  SendPort csendPort11;
  SendPort csendPort12;

  // Child 1
  receivePort1.listen((msg) {
    if (csendPort1 == null) {
      csendPort1 = msg;
    } else if (csendPort11 == null) {
      csendPort11 = msg;
    } else {
      print('$msg');`enter code here`
    }
  });

  bool child1 = false;
  Isolate.spawnUri(Uri.parse('child.dart'), [], receivePort1.sendPort).then((isolate) {
    print('Child 1 isolate spawned');
    new Timer.periodic(const Duration(milliseconds: 500), (t) {
      if (csendPort11 != null && csendPort12 != null && child1 == false) {
        child1 = true;
        csendPort12.send(csendPort11);
      } else {
        csendPort1.send('Parent-Child1: ${counter++}');
      }
    });
  });

  // Child 2
  receivePort2.listen((msg) {
    if (csendPort2 == null) {
      csendPort2 = msg;
    } else if (csendPort12 == null) {
      csendPort12 = msg;
    } else {
      print('$msg');
    }
  });

  bool child2 = false;
  Isolate.spawnUri(Uri.parse('child.dart'), [], receivePort2.sendPort).then((isolate) {
    print('Child 2 isolate spawned');
    new Timer.periodic(const Duration(milliseconds: 500), (t) {
      if (csendPort11 != null && csendPort12 != null && child2 == false) {
        child2 = true;
        csendPort11.send(csendPort12);
      } else {
        csendPort2.send('Parent-Child2: ${counter++}');
      }
    });
  });
}

Child Code:

import 'dart:isolate';
import 'dart:async';

int pcounter = 0;
int ccounter = 0;

SendPort csendPort;
void handleTimeout() {
  csendPort.send("${ccounter++}");
}

main(List<String> args, SendPort psendPort) {
  // Parent Comm
  ReceivePort creceivePort1 = new ReceivePort();
  psendPort.send(creceivePort1.sendPort);

  creceivePort1.listen((msg) {
    psendPort.send('Child-Parent: ${pcounter++} - ${msg}');
  });

  // Child-Child Comm
  ReceivePort creceivePort2 = new ReceivePort();
  psendPort.send(creceivePort2.sendPort);

  creceivePort2.listen((msg) {
    if (csendPort == null) {
      csendPort = msg;
      csendPort.send("${ccounter++}");
    } else {
      print("Child-Child: $msg");
      var duration = const Duration(milliseconds: 2000);
      new Timer(duration, handleTimeout);
    }
  });
}

HTML Code:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="scaffolded-by" content="https://github.com/google/stagehand">
    <title>WebIsolateTest</title>
    <link rel="stylesheet" href="styles.css">
    <script defer src="main.dart" type="application/dart"></script>
    <script defer src="packages/browser/dart.js"></script>
</head>

<body>

  <div id="output"></div>

</body>
</html>
Canner answered 7/3, 2016 at 18:32 Comment(1)
The above code works with dart sdk version 1.14.2 and on Chrome, Firefox and IE11Canner
T
0

You can now use the MessageBox class to communicate the other way around. This code sends a message from the Isolate code as soon as it receives the Sink end of the MessageBox. Main thread receives the messages sent from the Isolate and prints it on the console of Dartium. Once you receive the Sink you can launch your game logic and send updates using the sink object received.

import 'dart:html';
import 'dart:isolate';

void main() {
  IsolateSink isolateSink = streamSpawnFunction(myIsolateEntryPoint);
  MessageBox isolateMessageBox = new MessageBox();
  isolateSink.add(isolateMessageBox.sink);
  isolateMessageBox.stream.listen((String data) {
    print(data);
  });
}

void myIsolateEntryPoint() {
  stream.listen((IsolateSink messageBoxSink) {
    messageBoxSink.add("Test");
  });
}
Tax answered 22/7, 2013 at 10:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.