6
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

いまさらHTML5 (Workers編)

Posted at

はじめに

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を受け付けなくなります。

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(setTimeoutsetInterval)が使えます。
これを利用して、開始、停止を行えるようにしてみました。

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

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本体に依存しそうなので、切り分けが難しい気がしました。

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

6
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?