【ハウツー】

"重たいJavaScript処理"もこれで解決! - Google Gearsのワーカプールを試す

3 それでは実装!

    白石俊平  [2007/06/08]

    サンプルコードの解説

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

    今回作成したサンプル

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

    関連記事

    関連サイト

    関連したタグ

    新着記事

    特設サイトの情報

      人気記事

      一覧

        イチオシ記事

        新着記事

        特別企画

        マイナビニュースマガジン