いまさらHTML5 （Workers編）


## はじめに

Workerオブジェクトなのに、W3CにもWorkersとなっています。


## 処理に時間がかかる

Workerオブジェクトは、`new Worker(url);`と他ファイルのjsなどを指定しますが、
今回は、同一HTMLにしたかったので、Blobオブジェクトにタグ内のHTMLを入れ
それを`URL.createObjectURL`にして、Workerオブジェクトを生成しています。
（IEでは、エラーが発生して動きませんでした。IEのバグらしいです。）

素数を100万個求める演算をするのですが、Worker内で演算した場合、他のボタンが押せますが、
直接演算した場合は、他のボタンが押せなくなります。

また、FireFoxでは直接演算した場合、Scriptの遅延警告が出ました。

親とWorkerは、`postMessage`を利用して、双方向へデータを渡します。
イベント受信に`onmessage`に設定した関数にデータが来ますので、`data`プロパティからデータを取り出します。

また、Worker停止は、親からは`terminate`、Worker内部からは`close`で行います。
これらで停止すると、`postMessage`を受け付けなくなります。

```html:workers01.html
<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>Workers Sample</title>
    <script type="text/javascript" src="./jquery.js"></script>
    <script id="worker" type="text/javascript">
        // Workerメッセージ受信
        self.onmessage = function (event) {
            var data = event.data;
            var before = new Date();
            var primes = getPrimes(data);
            var after = new Date();
            self.postMessage({ primes: primes, before: before, after: after });
        }
        // 素数取得
        function getPrimes(count) {
            var primes = null;
            var cnt = parseInt(count);
            if (cnt != NaN) {
                primes = [];
                if (cnt > 0) {
                    primes.push(2);
                    var num = 3;
                    while (primes.length < cnt) {
                        var isPrime = true;
                        for (var i = 1; i < primes.length; i++) {
                            if ((num % primes[i]) == 0) {
                                isPrime = false;
                                break;
                            }
                            if (Math.ceil(num / primes[i]) < primes[i]) {
                                break;
                            }
                        }
                        if (isPrime) {
                            primes.push(num);
                        }
                        num += 2;
                    }
                }
            }
            return primes;
        }
    </script>
    <script type="text/javascript">

        // 素数の数
        var PRIME_NUM = 1000000;

        // Workerオブジェクト
        var worker = null;

        // WorkerソースURL
        var workerUrl = null;

        // 初期処理
        function init() {
            var src = $("#worker").html();
            var type = $("#worker").attr("type");
            var blob = new Blob([src], { type: type });
            workerUrl = URL.createObjectURL(blob);
            // 各ボタンにイベント設定
            $("[data-name='btn1']").click(workerStart);
            $("[data-name='btn2']").click(workerStop);
            $("[data-name='btn3']").click(direct);
        }

        // Worker取得
        function getWorker() {
            if (worker == null) {
                worker = new Worker(workerUrl);
                worker.onmessage = onMessage;
                worker.onerror = onError;
            }
            return worker;
        }

        // Workerメッセージ受信
        function onMessage(event) {
            var data = event.data;
            $("[data-name='log']").prepend(
                $("<div>").text("Message: len=" + data.primes.length
                + ",diff=" + (data.after.getTime() - data.before.getTime())
                + ",last=" + data.primes[data.primes.length - 1]));
        }

        // Workerエラー受信
        function onError(event) {
            $("[data-name='log']").prepend(
                $("<div>").text("Error:" + event.message +
                "\n" + event.filename + "\n" + event.lineno));
            workerStop();
        }

        // Worker開始
        function workerStart() {
            getWorker().postMessage(PRIME_NUM);
        }

        // Worker停止
        function workerStop() {
            if (worker != null) {
                worker.terminate();
                worker = null;
            }
        }

        // 直接実行
        function direct() {
            var before = new Date();
            var primes = getPrimes(PRIME_NUM);
            var after = new Date();
            $("[data-name='log']").prepend(
                $("<div>").text("Direct: len=" + primes.length
                + ",diff=" + (after.getTime() - before.getTime())
                + ",last=" + primes[primes.length - 1]));
        }

        // 初期処理登録
        $(init);
    </script>
</head>
<body>
    <button data-name="btn1">Worker START</button>
    <button data-name="btn2">Worker STOP</button>
    <button data-name="btn3">Direct Execute</button><br />
    <div data-name="log"></div>
</body>
</html>
```

## Timer

Worker内部では、DOMへの書き込みが出来ないなどの制約がありますが、
Timer（`setTimeout`や`setInterval`）が使えます。
これを利用して、開始、停止を行えるようにしてみました。

0~99の乱数回数を表示するサンプルです。

```html:workers02.html
<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>Workers Sample</title>
    <style type="text/css">td { border: 1px solid black; padding: 0.5em }</style>
    <script type="text/javascript" src="./jquery.js"></script>
    <script id="worker" type="text/javascript">
        var timer = null;
        // Workerメッセージ受信
        self.onmessage = function (event) {
            if (timer != null) {
                clearTimeout(timer);
                timer = null;
            } else {
                timer = setTimeout("rnd()", 10);
            }
        }
        var RANGE = 100;
        function rnd() {
            var data = {};
            for (var i = 0; i < RANGE; i++) {
                data["n" + i] = 0;
            }
            for (var i = 0; i < 1000000; i++) {
                var r = Math.floor(Math.random() * RANGE);
                data["n" + r] = data["n" + r] + 1;
            }
            postMessage(data);
            timer = setTimeout("rnd()", 10);
        }
    </script>
    <script type="text/javascript">
        var rndData = null;

        var worker = null;
        // 初期処理
        function init() {
            rndData = {};
            for (var i = 0; i < RANGE; i++) {
                rndData["n" + i] = 0;
            }
            var src = $("#worker").html();
            var type = $("#worker").attr("type");
            var blob = new Blob([src], { type: type });
            var workerUrl = URL.createObjectURL(blob);
            worker = new Worker(workerUrl);
            worker.onmessage = onMessage;
            worker.onerror = onError;
            worker.postMessage("");
            $("[data-name='btn']").click(btn);
        }

        function btn() {
            var text = $("[data-name='btn']").text();
            if (text == "STOP") {
                $("[data-name='btn']").text("START");
            } else {
                $("[data-name='btn']").text("STOP");
            }
            worker.postMessage("");
        }

        // Workerメッセージ受信
        function onMessage(event) {
            var data = event.data;
            var table = $("<table>").css("width", "100%");
            var trs = [];
            for (var i = 0; i < 10; i++) {
                trs.push($("<tr>"));
            }
            for (var i = 0; i < RANGE; i++) {
                rndData["n" + i] = rndData["n" + i] + data["n" + i];
                trs[i % 10].append($("<td>").text(i)).append($("<td>").text(rndData["n" + i]));
            }
            for (var i = 0; i < 10; i++) {
                table.append(trs[i]);
            }
            $("[data-name='result']").empty();
            $("[data-name='result']").append(table);
        }

        // Workerエラー受信
        function onError(event) {
            $("[data-name='log']").empty();
            $("[data-name='log']").prepend(
                $("<div>").text("Error:" + event.message +
                "\n" + event.filename + "\n" + event.lineno));
            workerStop();
        }

        // 初期処理登録
        $(init);
    </script>
</head>
<body>
    <button data-name="btn">STOP</button>
    <div data-name="result"></div>
    <div data-name="log"></div>
</body>
</html>
```

## 感想

どんなサンプルを作ったらいいのかすごく悩みました。
重い処理で、「Script応答なし」系のエラーを出したくない場合は
有効だと思いますが、実際どのくらいの処理速度かはブラウザのバージョンや、PC本体に依存しそうなので、切り分けが難しい気がしました。

そして、今回は支離滅裂な感じになってしまった。