2012年2月21日火曜日

Playing with Dartium part 2

ひき続き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());
         });
    }
    

0 件のコメント:

コメントを投稿