Darts neue Isolate API
Eines meiner Lieblingsfeatures in Google Dart sind die Isolates (eine Art Multithreading Feature für Dart, falls Sie es noch nicht kennen. Jedoch war die erste Version der API noch sehr komplex. Sie erinnerte sehr stark an das Java Threading und war generell sehr schwer zu erlernen. Die Dart-Entwickler haben jedoch recht bald angekündigt, eine Überarbeitung vorzunehmen, was mittlerweile getan ist.
Mit der alten API musste man noch die Isolate-Klasser erweitern, um ein Isolate zu erstellen. Dabei wurde zwischen “Heavy” und “Light” Isolates unterschieden. Das ist alles Vergangenheit. Die Dart VM entscheidet mittlerweile für den Entwickler, welche Art von Isolate angemessen ist. Außerdem muss nicht mehr von Isolate abgeleitet werden, jetzt arbeitet man mit Funktionen. Das Ergebnis führt zu sehr viel besser lesbaren Code. Ein Beispiel.
Zunächst muss das Isolate-Packet importiert werden. Auch das ist neu, denn vorher haben sich die Isolate Klassen im Core befunden.
#import('dart:isolate');
Dann kann der Spaß beginnen!
Zunächst definiere ich eine Funktion, die als neues Isolate agieren soll.
process() {
port.receive((var message, SendPort replyTo) {
print ("Got message: ${message}");
replyTo.send("close");
port.close();
});
}
Es muss sich hierbei um eine Top-Level Funktion handeln und darf keinen Rückgabewert haben. “Void” würde daher nicht funktionieren. In Zeile 2 sieht man, dass ich auf den “port” zugreife. Es handelt sich hier um eine get-Methode, die den Port dieser Funktion zurückliefert. Über Ports kann man mit anderen Isolates kommunizieren.
Dieser Port kann eien Funktion ausführen, wenn eine Nachricht erhalten wurde. Dessen Argumente sind dann die eigentliche Nachricht. Eine Nachricht kann aus null, num, bool, double oder String bestehen. Auch Listen und Maps, die diese Typen beinhalten, sind erlaubt. Sogar Objekte sind möglich, wenn die Isolates im selben Prozess laufen (vorher auch als “light” Isolate) bekannt. Die Dokumentation sagt dazu, diese ist der Fall wenn das Isolate mit “spawnFunction” erzeugt wurde.
Zusätzlich zur Nachricht erhalten wir auch noch einen SendPort, was eine Art Rückkanal zum sendenden Isolate darstellt.
In meiner Funktion drucke ich eine Nachricht, sende einen “close”-String zurück und schließe im Nachhinein den Port. Das Isolate ist dann bereit in den Müll zu wandern und die VM sollte dies auch erledigen. Es ist wichtig, die Ports zu schließen, da sonst der Code für immer läuft und den Speicher unnötig belastet.
Soweit zur Isolate-Funktion. Sehen wir uns an, wie wir die Funktion dann eigentlich erzeugen (spawn).
main() {
port.receive((var message, SendPort replyTo) {
print("Got message: ${message}");
port.close();
});
SendPort sender = spawnFunction(process);
sender.send("start", port.toSendPort());
}
Dieser Code läuft in der Main-Methode. Die Main-Methode ist auch nur ein Isolate, deswegen habe ich hier auf einen Port zugriff. Ich lege wieder eine Funktion als Empfänger fest, die abermals eine Nachricht druckt und den Port im Anschluß schließt. Sobald der Main-Port schließt, ist die Anwendung beendet.
Dann rufe ich die “spawnFunction()” Methode auf die die bereits beschriebene Top-Level-Funktion als Argument erhält. Ich verwende keine Klammern. Der Grund ist, dass ich eine Referenz zur Funktion übergeben möchte, und nicht dessen Ergebnis. Die eigentliche Durchführung soll ja im eigenen Isolate erfolgen, und zwar durch die spawnFunction-Methode.
Die spawnFunction-Methode liefert einen SendPort, den ich in der letzten Zeile benutze, um meine erste Nachricht zu senden. Mein zweites Argument ist der Rückkanal für das zweite Isolate. Jetzt kann ich also Antworten meiner “process”-Funktion erhalten.
Das war’s!
Ich habe festgestellt, das die Funktion nicht gespawned wird, when die port.receive() Methode keinen Handler erhält. Zusätzlich darf man nicht vergessen, dass die Funktion die gespawned werden soll, kein “void” als Rückgabewert erhält. Zu guter Letzt habe ich festgestellt, dass der SendPort – obwohl er in der API als optional markiert war – immer vorhanden oder null sein muss.
Fall Sie gerne mit Futures arbeiten möchten: auch dafür gibt es Unterstützung im SendPort. Es ist außerdem möglich einen Isolate von einer URI zu spawnen. Mehr dazu bieten die Docs.
Die API ist meiner Meinung nach ein Schritt in die richtige Richtung. Alles ist einfacher und benötigt weniger Code. Mal sehen, wie das in ein paar Monaten so aussieht. Es soll wohl auch bald Isolates geben, die auf dem gleichen DOM Stück operieren können, was die Geschwindigkeit von Webapps ziemlich steigern dürfte.
