サンプルコードの解説
今回作成したサンプルは、以下のような画面だ。
今回作成したサンプル |
「非同期処理」ボタンをクリックすると、ワーカプールを使用してバックグラウンドで「重たい処理」を実行する。「同期処理」の場合はワーカプールを使用しない。UIのブロックを防ぐ、ということを体感するための比較用だ。「重たい処理」が完了すると、ボタンの下に所要時間を表示する。
今回、「重たい処理」として行っているのは、Gearsのデータベース機能を用いてテーブルを作成した後、そのテーブルに新しい行を100件追加する、という処理だ。オンラインからオフラインに移行するとき、メモリ上のデータをローカルに保存する、といった処理を想定した。
以下がそのサンプルコードだ。
index.html
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=Shift_JIS" />
<!-- gears_init.jsの読み込み -->
<script src="gears_init.js"></script>
<script type="text/javascript"><!--
// ワーカプールのインスタンス
var workerPool;
// 作成したワーカのID
var childId;
/**
* ドキュメントの読み込み完了時に呼び出されます。
*/
function init() {
if (!window.google || !google.gears) {
return;
}
// (1) ワーカプールの作成
workerPool = google.gears.factory.create('beta.workerpool', '1.0');
}
/**
* 「重たい」処理。GearsのDBにテーブルを作成し、データをn件挿入します。
*/
function heavyTask() {
var start = new Date().getTime();
var database = google.gears.factory.create('beta.database', '1.0');
database.open("workerPoolTest");
database.execute("drop table if exists workerPoolTest");
database.execute(
"create table if not exists workerPoolTest (" +
" num1 integer not null, num2 integer not null" +
")");
for (var i = 0; i < 100; i++) {
database.execute(
"insert into workerPoolTest (num1, num2) values (?, ?)", [i, i * i]);
}
var end = new Date().getTime();
return (end - start);
}
/**
* heavyTask関数の同期呼び出しを行います。
*/
function run() {
var time = heavyTask();
document.getElementById("message").innerHTML = "処理終了。経過時間:[" + time + "] ms.";
}
// ========================================= 以下、非同期処理
/**
* heavyTask関数の非同期呼び出しを行います。
*/
function runInBackground() {
// (2) ワーカプールにメッセージハンドラを登録
workerPool.onmessage = parentHandler;
// (3) ワーカが実行するスクリプト
var workerScript =
String(heavyTask) + String(childHandler) + "google.gears.workerPool.onmessage = childHandler;";
// (4) ワーカ作成
childId = workerPool.createWorker(workerScript);
// (5) ワーカにメッセージ送信
workerPool.sendMessage("", childId);
}
/**
* メインページのイベントハンドラ
*/
function parentHandler(msg, sender) {
document.getElementById("message").innerHTML = "処理終了。経過時間:[" + msg + "] ms.";
}
/**
* (6) ワーカのイベントハンドラ
*/
function childHandler(msg, sender) {
var time = heavyTask();
google.gears.workerPool.sendMessage(String(time), sender);
}
--></script>
</head>
<body onload="init()">
<h2>ワーカプールのテスト</h2>
<input type="button" value="同期処理" onclick="run()"/>
<input type="button" value="非同期処理" onclick="runInBackground()"/>
<input type="checkbox"/>← UIがフリーズしていないかどうか触ってみる
<div id="message"></div>
</body>
</html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=Shift_JIS" />
<!-- gears_init.jsの読み込み -->
<script src="gears_init.js"></script>
<script type="text/javascript"><!--
// ワーカプールのインスタンス
var workerPool;
// 作成したワーカのID
var childId;
/**
* ドキュメントの読み込み完了時に呼び出されます。
*/
function init() {
if (!window.google || !google.gears) {
return;
}
// (1) ワーカプールの作成
workerPool = google.gears.factory.create('beta.workerpool', '1.0');
}
/**
* 「重たい」処理。GearsのDBにテーブルを作成し、データをn件挿入します。
*/
function heavyTask() {
var start = new Date().getTime();
var database = google.gears.factory.create('beta.database', '1.0');
database.open("workerPoolTest");
database.execute("drop table if exists workerPoolTest");
database.execute(
"create table if not exists workerPoolTest (" +
" num1 integer not null, num2 integer not null" +
")");
for (var i = 0; i < 100; i++) {
database.execute(
"insert into workerPoolTest (num1, num2) values (?, ?)", [i, i * i]);
}
var end = new Date().getTime();
return (end - start);
}
/**
* heavyTask関数の同期呼び出しを行います。
*/
function run() {
var time = heavyTask();
document.getElementById("message").innerHTML = "処理終了。経過時間:[" + time + "] ms.";
}
// ========================================= 以下、非同期処理
/**
* heavyTask関数の非同期呼び出しを行います。
*/
function runInBackground() {
// (2) ワーカプールにメッセージハンドラを登録
workerPool.onmessage = parentHandler;
// (3) ワーカが実行するスクリプト
var workerScript =
String(heavyTask) + String(childHandler) + "google.gears.workerPool.onmessage = childHandler;";
// (4) ワーカ作成
childId = workerPool.createWorker(workerScript);
// (5) ワーカにメッセージ送信
workerPool.sendMessage("", childId);
}
/**
* メインページのイベントハンドラ
*/
function parentHandler(msg, sender) {
document.getElementById("message").innerHTML = "処理終了。経過時間:[" + msg + "] ms.";
}
/**
* (6) ワーカのイベントハンドラ
*/
function childHandler(msg, sender) {
var time = heavyTask();
google.gears.workerPool.sendMessage(String(time), sender);
}
--></script>
</head>
<body onload="init()">
<h2>ワーカプールのテスト</h2>
<input type="button" value="同期処理" onclick="run()"/>
<input type="button" value="非同期処理" onclick="runInBackground()"/>
<input type="checkbox"/>← UIがフリーズしていないかどうか触ってみる
<div id="message"></div>
</body>
</html>
このサンプルの実行方法は本稿の最後で説明する。
では、ポイントとなる部分を解説していこう
(1) ワーカプールの作成
workerPool = google.gears.factory.create('beta.workerpool', '1.0');
ワーカプールを作成するには、google.gears.factory.create('beta.workerpool', '1.0')
というコードを用いる。このコードは、ローカルサーバやデータベースなど、他のGearsモジュールを使用するのと同様のコードだ。
(2) ワーカプールにメッセージハンドラを登録
workerPool.onmessage = parentHandler;
ワーカを作成する前に、ワーカから送信されたメッセージを受信するためのハンドラを登録しておく必要がある。これは、たとえワーカとメッセージ通信を行わないとしても必須の処理だ。メッセージハンドラの書式は決まっており、第一引数でメッセージ、第二引数で送信元ワーカのIDを受け取る。今回は、子ワーカから「処理にかかった時間」をメッセージとして受信し、画面にテキストを表示する。なお、子ワーカ(バックグラウンドタスク)から、経過時間を受け取り画面に表示している部分は以下のようになる。
function parentHandler(msg, sender) {
document.getElementById("message").innerHTML = "処理終了。経過時間:[" + msg + "] ms.";
}
(3) ワーカが実行するスクリプト文字列の作成
var workerScript =
String(heavyTask) + String(childHandler) +
"google.gears.workerPool.onmessage = childHandler;";
子ワーカ(バックグラウンドタスク)が実行するコードを文字列として作成している。注意すべきなのは、子ワーカとは変数はおろか、定義してある関数も共有できないということ。つまり、ここで構築している文字列内に、子ワーカが使用する関数の定義などを全て含める必要がある。
ここでは、String(関数オブジェクト)
という見慣れない方法を使用し、heavyTask
関数やchildHandler
関数などの定義を文字列化している。
(4) ワーカ作成
childId = workerPool.createWorker(workerScript);
準備が整ったので、実際にワーカを作成している。(3)で作成した文字列を引数に指定して、WorkerPool#createWorker()
関数を呼び出すことで、ワーカの作成は完了だ。その戻り値は、今回作成したワーカのIDとなるので、保存しておいてメッセージ送信に使用する。
(5) ワーカにメッセージ送信
workerPool.sendMessage("", childId);
(4)で取得したワーカのIDを引数にして、ワーカにメッセージを送信している。ここでは、送信するメッセージ文字列は重要ではないので空文字を指定している。送信したメッセージを受信する側の処理は(6)を参照してほしい。
(6) ワーカのイベントハンドラ
function childHandler(msg, sender) {
var time = heavyTask();
google.gears.workerPool.sendMessage(String(time), sender);
}
「重たい処理」(heavyTask)を行って、その処理時間をsender(送信元)、つまりUI操作を行うことのできる親ワーカに送信している。ここで送信したメッセージを受信するのは、(2)に示したparentHandler
関数だ。また、注意してほしいのはこのハンドラ内でgoogle.gears.workerPool
という変数を使用していること。この変数はワーカプールを参照しており、(状態を共有できない)各ワーカが共通で使用できる暗黙的な変数だ。
以上でポイントの解説は終わりだ。実際にこのサンプルを動作させると、ワーカプールを使用するとUIが全くブロックされず、ユーザは操作をずっと継続できる。
サンプルの実行方法
これでGoogle Gearsのワーカプールの解説を終わりとする。長時間かかる計算処理や、今回のサンプルのようなデータベースIOなどは、ワーカプールを適切に用いればユーザビリティを大幅に向上させることができる。これまで、「時間がかかりすぎるから」という理由でサーバに処理を任せていた部分なども、ブラウザ内で処理させることでアーキテクチャをすっきりさせることができる可能性がある。
最後に、今回使用したサンプルを実行する方法を示しておく。
Gearsを使用したアプリケーションは、<script>タグを用いてgears_init.js
というJavaScripファイルを読み込む必要があるのは説明したとおりだ。
サンプルを実行するには、こちらのページの「Get gears_init.js」というリンクから、gears_init.jsをダウンロードして保存しておく必要がある。
以下に示すサンプル全文をコピー&ペーストしたものを適当なファイル名で保存し、gears_init.jsと同じディレクトリに保存してほしい。
そのディレクトリをApache HTTP ServerなどのHTTPサーバで公開し、GearsがインストールされたブラウザでWebページにアクセスすれば、実行できるはずだ。