サンプルコードの解説

今回作成したサンプルは、以下のような画面だ。

今回作成したサンプル

「非同期処理」ボタンをクリックすると、ワーカプールを使用してバックグラウンドで「重たい処理」を実行する。「同期処理」の場合はワーカプールを使用しない。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ページにアクセスすれば、実行できるはずだ。