ひき続きDartiumでお遊び。今回はIsolateを試してみた。が、結論から言うと目論みははずれてうまくいなかったです。
Isolate
Dartはつねにシングルスレッドで実行される(仕様書にも"Dart code is always single threaded."て書いてある)んだけど、何らかの処理を並行して実行したい場合というのが出てくる。他の言語だったらスレッドなりの出番なんだけど、dartではIsolateという仕組みを使うみたい。
Isolate自体はスレッドのようなものだと考えれば良くて(実際にスレッドをつくるかどうかはIsolateの設定次第)、使いかたはIsolateクラスをextendしてmain関数を定義するだけ。まあその辺の作法の詳細はドキュメントかサンプルを参照。
RemarkDisplayerIsolate
ということで前回つくったRemarkDisplayerをIsolate化して使おうと思ったんだけど、その過程でちょっと予想外なエラーにぶちあったった。Dartiumごとクラッシュしたのでログだけ載せる。
Unhandled exception: DOM access is not enabled in this isolate 0. Function: 'Utils.window' url: '/mnt/data/b/build/slave/dartium-lucid64-inc/build/src/dart/client/dom/src/native_DOMImplementation.dart' line:20 col:3 1. Function: '::get:window' url: '/mnt/data/b/build/slave/dartium-lucid64-inc/build/src/dart/client/dom/src/native_GlobalProperties.dart' line:6 col:36 ...
IsolateからはDOMに触れないって話。エラーメッセージ読むかぎりだと設定で有効化できそうな雰囲気もあるけど、予想ではたぶんできない。C#でGUIを作るときもそうだけど、バックグラウンドのスレッドからは直接UI要素に触らせてくれない場合が多い。
感想
まあとりあえずIsolateの使いかたそのものは何となくわかったのでよしとする。でもIsolate自体はあんまり使いやすくないな…わかりづらい。GoのChannelみたいにPortが双方向だともっとスッキリしてわかりやすいと思うんだけども。コード
途中で例外飛ぶけど、失敗例としてコードを掲載しておく。
- RemarkDisplayer.dart
#library("RemarkDisplayer"); #import("dart:html"); class MessageTypes { static final int ERROR = -1; static final int OK = 0; static final int SETUP = 1; static final int INIT = 2; static final int DISPLAY = 3; } class DisplayerState { static final int SETUP = 1; static final int INITIALIZED = 2; static final int DISPLAYING = 3; } /** * Class that attempted to manipulate DOM in an Isolate. */ class RemarkDisplayerIsolate extends Isolate { RemarkDisplayerIsolate() {} void main() { port.receive((message, SendPort replyTo) { dispatch(message, replyTo, port.toSendPort()); }); } void dispatch(var message, SendPort replyTo, SendPort myPort) { int action = message['action']; var arg = message['arg']; switch(action) { case MessageTypes.SETUP: _displayer = new RemarkDisplayer(arg); replyTo.send(DisplayerState.SETUP, myPort); break; case MessageTypes.INIT: _displayer.initialize(arg); // EXCEPTION FIRED HERE replyTo.send(DisplayerState.INITIALIZED, myPort); break; case MessageTypes.DISPLAY: _displayer.display(arg); replyTo.send(DisplayerState.DISPLAYING, myPort); break; } } RemarkDisplayer _displayer; } /** * Class that manages remark display */ class RemarkDisplayer { RemarkDisplayer(int numberOfRemarks) { _numberOfRemarks = numberOfRemarks; _remarkList = new List<Element>(); _currentRemark = 0; } /** * Create remark nodes under the tag with the given ID */ void initialize(String stageID) { var stage = document.query(stageID); for (int i=0; i<_numberOfRemarks; i++) { var tag = '<pre class="remark" id="remark${i}" draggable="true"/>'; var elem = new Element.html(tag); stage.nodes.add(elem); _remarkList.add(elem); } } /** * Display given remark at the current node */ void display(String remark) { var node = _remarkList[_currentRemark]; var durationMS= 5000; node.innerHTML = remark; node.style.visibility = "visible"; node.style.animationName = "fade, hslide"; node.style.animationDuration = "${durationMS}ms"; node.style.animationTimingFunction = "linear"; node.style.animationFillMode = "forwards"; // Replace node with a clone to restart animation var newNode = node.clone(true); node.replaceWith(newNode); _remarkList[_currentRemark] = newNode; // proceed to next remark _currentRemark++; if (_currentRemark >= _numberOfRemarks) { _currentRemark = 0; } } int _numberOfRemarks; get numberOfRemarks() => _numberOfRemarks; List<Element> _remarkList; int _currentRemark; }
- main.dart
#library('displayer'); #import("dart:html"); #import("RemarkDisplayer.dart"); void dispatch(var message, SendPort replyTo, SendPort myPort) { switch(message) { case DisplayerState.SETUP: var msg = { "action" : MessageTypes.INIT, "arg" : "#stage" }; replyTo.send(msg, myPort); break; case DisplayerState.INITIALIZED: var msg = { "action" : MessageTypes.DISPLAY, "arg" : "adsfasfas" }; replyTo.send(msg, myPort); break; case DisplayerState.DISPLAYING: replyTo.close(); break; } } void main() { final int MAX_NUMBER_OF_REMARKS = 10; final receivePort = new ReceivePort(); receivePort.receive((var message, SendPort sendPort) { print("received ${message}"); dispatch(message, sendPort, receivePort.toSendPort()); }); new RemarkDisplayerIsolate().spawn().then((SendPort sendPort) { var msg = { "action" : MessageTypes.SETUP, "arg" : MAX_NUMBER_OF_REMARKS }; sendPort.send(msg, receivePort.toSendPort()); }); }