はじめに
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
を受け付けなくなります。
<!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の乱数回数を表示するサンプルです。
<!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本体に依存しそうなので、切り分けが難しい気がしました。
そして、今回は支離滅裂な感じになってしまった。