ActionScript Thread Library 1.0 (そうめん) は、タスクシステムと Java のスレッドモデルをベースとした疑似スレッドライブラリです。 複雑で冗長になりがちな、イベント処理や非同期処理、リアルタイム処理を、分かりやすくスマートに記述することを可能にします。 もちろん、割り込みやキャンセル、例外処理もバッチリです。スレッドを使って、イベントリスナー地獄から抜け出しましょう。

※ActionScript Thread Library は、名前こそスレッドですが、実体はタスクシステムにスレッドモデルを搭載したものなので、 「スレッド」と考えるよりは、「タスクシステム」だと考えた方が理解が早いでしょう。このドキュメントもその前提でお読みください。


1. スレッドライブラリの導入

1-1. スレッドとは

はじめにこの処理をして、次にこの処理をして...、という処理の流れを「スレッド (Thread)」と呼びます。英単語の「Thread」には「糸」「縫い糸」「筋道」「脈絡」といった意味があります。

ActionScript 3.0 (FlashPlayer) は、処理の流れがひとつしかない「シングルスレッド」で、かつイベントを介して処理を進める「イベント駆動」を採用しているため、 データのロード完了を待つ、ユーザーがマウスをクリックするのを待つ、といった非同期処理が入るととたんにコードが複雑になる傾向があります。

ActionScript Thread Library は、これを緩和するために、同時に複数の処理の流れ (すなわち複数のスレッド) が動く「マルチスレッド」を擬似的に実現し、かつ非同期処理が入る場合でも、 できるだけイベントのことを考えずに同期的に処理を書ける機構を提供します。

1-2. インストール

1-2-1. Flash CS3 の場合 (mxp パッケージ)

  1. Adobe Extensiton Manager のインストールが済んでいない場合、インストールします。
  2. ActionScript Thread Library プロジェクトページの「ダウンロード」へ行き、使用している OS にあわせて、「Thread-1.0-win.mxp」または「Thread-1.0-mac.mxp」をダウンロードします。
  3. ダウンロードしたmxpを実行します。この時、FlashCS3のConfigurationディレクトリの場所(標準でWindowsは「C: /Program Files/Adobe/Adobe Flash CS3/ja/Configuration」Macは「/Applications/Adobe Flash CS3/Configuration」です)を聞かれますので選択してください。
  4. インストールが無事完了すれば、Flash CS3 上のプロジェクトで ActionScript Thread Library (org.libspark.thread パッケージ) が使用可能になります。

1-2-2. Flex Builder の場合 (swc パッケージ)

  1. ActionScript Thread Library プロジェクトページの「ダウンロード」へ行き、「Thread-1.0.swc」をダウンロードします。
  2. Flex Builder 上で、ActionScript Thread Library を使用したいプロジェクトを開くか、作成します。
  3. プロジェクトのプロパティを表示し、「ActionScript ビルドパス」内の「ライブラリパス」タブを開きます。
  4. 「SWC の追加」ボタンをクリックし、表示されたダイアログ内で、ダウンロードした「Thread-1.0.swc」を指定して追加します。
  5. 「OK」ボタンを押して設定を保存すれば完了です。設定したプロジェクトで ActionScript Thread Library (org.libspark.thread パッケージ) が使用可能になります。

1-2-3. Flash Develop の場合 (swc パッケージ)

  1. ActionScript Thread Library プロジェクトページの「ダウンロード」へ行き、「Thread-1.0.swc」をダウンロードします。
  2. Flash Develop 上で、ActionScript Thread Library を使用したいプロジェクトを開くか、作成します。
  3. プロジェクトのプロパティを表示し、「Compiler Options」タブを開きます。
  4. 「SWC Libraries」の所に、ダウンロードした「Thread-1.0.swc」を指定して追加します。
  5. 「OK」ボタンを押して設定を保存すれば完了です。設定したプロジェクトで ActionScript Thread Library (org.libspark.thread パッケージ) が使用可能になります。

1-2-4. Flex SDK の場合 (swc パッケージ)

  1. ActionScript Thread Library プロジェクトページの「ダウンロード」へ行き、「Thread-1.0.swc」をダウンロードします。
  2. コンパイル時に、「library-path」オプションを使用して、ダウンロードした「Thread-1.0.swc」を追加します。このとき、「=」ではなく「+=」を使用することに注意して下さい。
    mxmlc -library-path+=Thread-1.0.swc MyApplication.as
  3. コンパイルするプロジェクト内で ActionScript Thread Library (org.libspark.thread パッケージ) が使用可能になります。

1-2-5. その他の場合 (ソースアーカイブ)

  1. ActionScript Thread Library プロジェクトページの「ダウンロード」へ行き、「Thread-1.0-src.zip」をダウンロードするか、 或は最新のソースコードを使用したい場合は、Subversion 経由で以下のコマンドを利用してリポジトリから直接ソースコードを取得します。
    $ svn co http://www.libspark.org/svn/as3/Thread/trunk/src
  2. 展開したソースコードの src フォルダを、使用したいプロジェクトのソースディレクトリにコピーするか、クラスパスの通った場所に配置します。
  3. プロジェクトで ActionScript Thread Library (org.libspark.thread パッケージ) が使用可能になります。

1-3. スレッドライブラリの初期化

ActionScript Thread Library を使用するためには、まず最初に Thread クラスinitialize メソッドを呼び出してライブラリを初期化する必要があります。 特にが無ければ問題ドキュメントクラスのコンストラクタで初期化するのが良いでしょう。

package
{
    import flash.display.Sprite;
    import org.libspark.thread.Thread;
    import org.libspark.thread.EnterFrameThreadExecutor;

    public class MyDocumentClass extends Sprite
    {
        public function MyDocumentClass()
        {
            Thread.initialize(new EnterFrameThreadExecutor());
        }
    }
}

initialize メソッドは、IThreadExecutor インターフェイスの実装クラスを引数に取ります。この設定は、「スレッドをいつ実行するか」を決める役割を持っています。 詳しくは、「2-6. スレッドの実行タイミングと IThreadExecutor」で解説します。

もし、プロジェクトが複数の SWF ファイルに分割されている場合、それぞれの SWF ファイルのドキュメントクラスでライブラリの初期化をしてしまうと、意図しない挙動をすることがあります。 これを防ぐために、既にライブラリが初期化済みかを表す isReady プロパティを用いて、既にライブラリが初期化されていた場合は、初期化をしないようにして下さい。

public class MyDocumentClass extends MovieClip
{
    public function MyDocumentClass()
    {
        if (!Thread.isReady) {
            Thread.initialize(new EnterFrameThreadExecutor());
        }
    }
}

2. スレッドの使用

2-1. スレッドの作成

スレッドを使うためには、まずスレッドを作らなければなりません。予め用意されているいくつかのスレッド (URLLoaderThreadTweenerThread) を使用する場合でも、それ単体で使用する場合というのは稀で、ほとんどの場合、「そのスレッドを使うためのスレッド」を自分で書くことになります。

独自のスレッドを書くためには、まず Thread クラスを継承したサブクラスを作成します。 そして、run メソッドをオーバーライドして処理を記述します。

次の例では、実行すると「Hello」と出力する、MyThread というスレッドを作成しています。

import org.libspark.thread.Thread;

public class MyThread extends Thread
{
    override protected function run():void
    {
        trace('Hello');
    }
}

2-2. スレッドの実行

スレッドを実行したい場合、まずはじめに、new 演算子を使用して実行したいスレッドのインスタンスを作成します。

var t:MyThread = new MyThread();

スレッドは、インスタンスを作成しただけでは実行されません。実行を開始したいタイミングで、 start メソッドを呼び出すことで、はじめて実行を開始します。

t.start();

MyThread クラスは実行すると「Hello」と出力するスレッドでしたので、start メソッドが呼び出されたあと、「Hello」と出力され、スレッドが終了します。

2-3. 実行関数

スレッドの実行が開始されると呼び出されるメソッドのことを、「実行関数」と呼びます。実行関数の中では、next メソッドを用いて、 次に実行する実行関数を設定することができ、スレッドの実行は実行関数が設定される限り継続します。

run メソッドをオーバーライドして処理を記述したことからも分かるように、実行関数はまずはじめに run メソッドに設定されます。 次の例では、run メソッドの中で next メソッドを用いて、world メソッドを次の実行関数に設定しています。

public class MyThread extends Thread
{
    override protected function run():void
    {
        trace('Hello');
        
        // 次の実行関数
        next(world);
    }
    
    private function world():void
    {
    	trace(', World!');
    }
}

このスレッドを実行すると、まずはじめに最初の実行関数である run メソッドが呼び出されて「Hello」が出力され、 続いて、次の実行関数である world メソッドが呼び出されて「, World!」が出力され、スレッドの実行が終了します。

実行関数が次々に呼び出されるというこの仕組みが、ActionScript Thread Library の最も基本的で大事な部分です。 next メソッドの他にも様々なメソッドが用意されており、それを使用することで、スレッドの動作をより細かく制御することができるようになります。 詳しくは、「2-5. スレッドの待機」や「4. スレッドの制御」で解説します。

2-4. 複数のスレッドの実行

ActionScript Thread Library は擬似的なマルチスレッドを実現するライブラリですので、もちろん同時に複数のスレッドを実行することが出来ます。 複数のスレッドを実行するには、実行したい分だけスレッドのインスタンスを作り、それぞれの start メソッドを呼び出して実行を開始します。 ひとつのスレッドのインスタンスに対して、start メソッドを複数回呼び出すことは出来ないので注意して下さい。

実行が開始されたスレッドは、そのスレッドが終了するまで、他のスレッドの干渉や影響を受けることなく、実行を続けます。

次の例では、決められた数になるまで、数字をカウントアップしながら表示するスレッドを、それぞれ「1」と「3」と「6」に設定して実行します。

public class CountThread extends Thread
{
    public funciton CountThread(n:uint)
    {
    	// いくつになるまでカウントするか
    	_n = n;
    	// 実際のカウント
    	_c = 0;
    }
    
    private var _n:uint;
    private var _c:uint;
    
    override protected function run():void
    {
    	// カウントを表示
    	trace(_c);
    	
    	// カウントがまだ決められた数まで達していなければ
    	if (++_c <= _n) {
    		// もう一度この実行関数を実行する (そうでなければ終了)
    		next(run);
    	}
    }
}
var c1:CountThread = new CountThread(1);
var c3:CountThread = new CountThread(3);
var c6:CountThread = new CountThread(6);

c1.start();
c3.start();
c6.start();

実行結果は次のようになります。みっつのスレッドが同時に動いてカウントを表示しつつも、 カウントが 1 になった段階と、3 になった段階で、c1 スレッドと c3 スレッドが終了するため、このような出力になります。

0
0
0
1
1
1
2
2
3
3
4
5
6

2-5. スレッドの待機

スレッドには、ある特定の状態になるまで、実行を中断して待機するための機能が存在します。実行を中断する、とは「実行関数の呼び出しを止める」ということで、 このような状態を「待機状態」と呼びます。

スレッドを待機させるメソッドはいくつかありますが、最も簡単な例が sleep メソッドです。 このメソッドは、指定された時間が経過するまでスレッドの実行を中断して待機させます。

次の例では、「Hello」と出力したあと、sleep メソッドを用いて 1 秒間待機し、「World」を出力します。

public class MyThread extends Thread
{
    override protected function run():void
    {
        trace('Hello');
        
        // 1 秒間待機
        sleep(1000);
        // 次の実行関数
        next(world);
    }
    
    private function world():void
    {
    	trace('World');
    }
}

注意しなければならないのは、sleep メソッドを呼び出したその時点でコードの実行が中断されるわけではないということです。 それではいつ実行が中断されるかというと、今実行中の実行関数 (ここでは run メソッド) の処理が全て終わった時にはじめて中断され、 ある状態になるまで (ここでは 1 秒経過するまで) 、次の実行関数の実行が遅延されます

すなわち、上の例では、sleep メソッドが呼び出されたあと、next メソッドによって次の実行関数 (world メソッド) が設定された上で、 待機状態に移行して実行を中断し、1 秒経過するのを待って実行を再開し、次の実行関数である world メソッドが呼び出される、という流れになります。

このように、ActionScript Thread Library では、途中にスレッドの待機が入る場合に、 待機後の処理を書きたい場合は、実行関数を分割して next メソッドで設定するのが定石となりますのでよく覚えておいて下さい。

sleep メソッド以外のメソッドについては、「4. スレッドの制御」や「5. モニタ機構」で解説します。

2-6. スレッドの実行タイミングと IThreadExecutor

スレッドを使いこなすためには、実行関数が実行されるタイミングというものを、ある程度意識する必要があります。 これには、「1-3. スレッドライブラリの初期化」でも出てきた、IThreadExecutor インターフェイスの実装クラスが深く関わっています。

ActionScript Thread Library には、標準で EnterFrameThreadExecutorIntervalThreadExecutor というふたつの IThreadExecutor インターフェイス実装クラスが用意されています。 これらはそれぞれ、「フレーム実行のタイミング (Event.ENTER_FRAME のタイミング)」と「指定された時間の間隔のタイミング」で、 その時点までに開始されている全てのスレッドの実行関数を呼び出すクラスです。

たとえば、初期化時に EnterFrameThreadExecutor を指定していて、スレッド A とスレッド B が開始されている場合、フレームが実行される (Event.ENTER_FRAME が発生する) 度に、スレッド A とスレッド B の実行関数が呼び出されることになります。

実行関数が呼び出される順番は、スレッドが開始された順です。ただし、スレッドに親子関係がある場合は、それが考慮されます。詳しくは「3-2. 親子関係のあるスレッドの実行順序」で解説します。

このように、ActionScript Thread Library では、スレッドの実行タイミングと実行順序は完全に決まっており、本当のマルチスレッドのように、 実行されるタイミングや、実行される順序が不定ということはありません。

2-7. 現在のスレッド

あるスレッドの実行関数が実行されているとき、そのスレッドは「現在実行されているスレッド (現在のスレッド)」として currentThread プロパティに設定されます。 スレッドの実行関数内で currentThread プロパティを参照したとき、それは必ず自分自身 (this) になります。

どのスレッドの実行関数も実行されていない、つまり現在実行されているスレッドが無い場合は、currentThread プロパティは null を返します。

「現在のスレッド」は、next メソッドをはじめとする、Thread クラスに静的 (static) なメソッドとして定義されているいくつかのメソッド呼び出しに於いて重要な意味を持ちます。 これらのメソッドは、「現在のスレッド」に対して設定をするようになっており、すなわちそれ以外のスレッドからは設定が出来ないようになっています。

たとえば、スレッド A とスレッド B が実行されているときに、スレッド B の実行関数から、スレッド A の next メソッドを呼び出して、スレッド A の次の実行関数を変更する、といったことは出来ません。

外部のスレッドによって意図せずスレッドの動作が変えられてしまわないように、このような設計になっています。

2-8. スレッドの終了処理

スレッドは、常に安全に終了することが求められます。たとえば、スレッドを実行する中で確保されたリソースがあれば、それはスレッドが終了する時にはきちんと解放されていることが望ましいでしょう。

このような終了処理を確実に行うために、ActionScript Thread Library には finalize メソッドが用意されています。 これは、「終了処理を行うための実行関数」です。

次に実行する実行関数が設定されなかった場合、スレッドはそのまま終了するのではなく、実際には finalize メソッド を次の実行関数に設定して実行し、 そこで次に実行する実行関数が設定されなかった場合に、本当に終了します。finalize メソッドは、例外が発生した場合でも実行されることが保証されているので、 確実に終了処理を行うことが出来ます (例外については「7. 例外」で解説します)。

次の例では、finalize メソッドを用いて、スレッドの実行中に確保した BitmapData が確実に解放されるようにしています。 実行関数 something の実行が終了すると、自動的に finalize メソッドが呼び出されます。

public class MyThread extends Thread
{
    private var _bitmapData:BitmapData;
    
    override protected function run():void
    {
        // BitmapData 確保
        _bitmapData = new BitmapData(128, 128);
    
        next(something);
    }
    
    private function something():void
    {
        // 何か処理をする
    }
    
    override protected function finalize():void
    {
        // BitmapData 解放
        _bitmapData.dispose();
    }
}

run メソッドからはじまる実行関数の実行の流れを「実行フェーズ」と呼び、実行フェーズが終了した後の、 finalize メソッドからはじまる実行関数の実行の流れを「終了フェーズ」と呼びます。 終了フェーズが終了すると、スレッドは終了します。スレッドが終了フェーズから実行フェーズに戻ることはありません。

2-9. スレッドの状態

スレッドはある時点で、以下のいずれかの状態を取ります。これらの値は ThreadState クラスで定義されており、実際の状態は state プロパティを通して知ることが出来ます。

スレッドが生成されると、まずはじめに、状態は「NEW」に設定されます。このあと、start メソッドによってスレッドが開始されると、状態は「RUNNABLE」に設定され、実行フェーズとなります。 「NEW」以外の状態のスレッドを start メソッドによって開始することはできず、万が一 start メソッドを呼び出してしまった場合、 IllegalThreadStateError 例外がスローされます。

wait メソッドjoin メソッド等の呼び出しによってスレッドが待機状態になる場合 (詳しくは「4. スレッドの制御」を参照してください) 状態は「WAITING」に設定されます。 このとき、制限時間が設定されるか、またはsleep メソッドの呼び出しであった場合、状態は「TIMED_WAITING」に設定されます。待機状態が解除されると、状態は元に戻ります。

スレッドが終了フェーズに移行すると、状態は「TERMINATING」に設定されます。スレッドが終了フェーズから実行フェーズに戻ることはないので、 同様に、状態が「TERMINATING」から「RUNNABLE」に戻ることはありません。ただし、「WAITING」や「TIMED_WAITING」になることは有り得ます。

終了フェーズが終わり、完全にスレッドが終了すると、状態は「TERMINATED」に設定されます。


3. スレッドの親子関係

3-1. スレッドの親子関係の決定

スレッドの親子関係は、スレッドの実行開始時に決定されます。あるスレッドの実行中に別のスレッドの実行を開始した (start メソッドを呼び出した) 場合、 実行を開始させた (start メソッドを呼び出した) スレッドは「親スレッド」となり、実行が開始された (start メソッドが呼び出された) スレッドはその「子スレッド」となります。

スレッドの親子関係は、実行順序と例外の伝播において重要になります。詳しくはそれぞれ「3-2. 親子関係のあるスレッドの実行順序」と「7. 例外」で解説します。

どのスレッドも実行されていない状態でスレッドの実行が開始された場合、つまり親スレッドがいない場合、そのスレッドは「トップレベルスレッド」となります。

次の例では、スレッド B はスレッド A によって開始されるため、スレッド A の子スレッドとなります。スレッド A には親スレッドがいないため、トップレベルスレッドとなります。

public class A extends Thread
{
    override protected function run():void
    {
    	// スレッド A によってスレッド B を開始する
        var b:B = new B();
        b.start();
    }
}

public class B extends Thread
{
    override protected function run():void
    {
    }
}
var a:A = new A();
a.start();

3-2. 親子関係のあるスレッドの実行順序

あるスレッドに子スレッドが存在する場合、そのスレッドよりも先に子スレッドが実行され、その後でそのスレッドが実行されます。 子スレッドが実行される順番は、子スレッドが開始された順番と同じになります。 この動作は、トップレベルスレッドからはじまり、再帰的に行われます。

次の例では、スレッド A の子スレッドとしてスレッド B とスレッド C をこの順番で開始しているため、スレッドが実行される際には「B→C→A」という順で実行され、 結果として「B」「C」「A」という出力の繰り返しが得られます。

public class A extends Thread
{
    override protected function run():void
    {
    	// スレッド A の子スレッドとしてスレッド B を開始する
        var b:B = new B();
        b.start();
        // スレッド A の子スレッドとしてスレッド C を開始する
        var c:C = new C();
        c.start();
        
        next(print);
    }
    
    private function print():void
    {
        trace('A');
        
        next(print);
    }
}

public class B extends Thread
{
    override protected function run():void
    {
        trace('B');
        
        next(run);
    }
}

public class C extends Thread
{
    override protected function run():void
    {
        trace('C');
        
        next(run);
    }
}
var a:A = new A();
a.start();

3-3. 孤児スレッド

子スレッドよりも先に親スレッドが終了した場合、その子スレッドは「孤児スレッド」となり、トップレベルスレッドとして再配置されます。 親スレッドが終了したからといって、子スレッドが強制的に終了するようなことはありません。


4. スレッドの制御

4-1. スレッドの終了待ち

あるスレッドのjoin メソッドを呼び出すと、そのスレッドが終了するまで、join メソッドを呼び出したスレッドを待機させることができます。 待機について詳しくは「2-5. スレッドの待機」を参照して下さい。

次の例では、スレッド MyThread の中で、「Start」を出力したあと、数字を 5 までカウントする CountThread を開始し、終了を待ってから「End」を出力します。

public class MyThread extends Thread
{
    override protected function run():void
    {
        trace('Start');
    	
        // CountThread の開始
        var count:CountThread = new CountThread();
        count.start();
        // CountThread の終了を待つ
        count.join();
        // 次の実行関数
        next(end);
    }
    
    private function end():void
    {
        trace('End');
    }
}

public class CountThread extends Thread
{
    private var _count:uint = 0;

    override protected function run():void
    {
    	// カウントを出力
        trace(_count);
    
        // カウントが 5 未満であれば再びこの実行関数を実行
        if (++_count <= 5) {
            next(run);
        }
    }
}
var t:MyThread = new MyThread();
t.start();

結果として、以下の出力が得られます。

Start
1
2
3
4
5
End

4-2. 指定時間の待機

sleep メソッドを呼び出すと、指定時間が経過するまで、sleep メソッドを呼び出したスレッドを待機させることができます。 待機について詳しくは「2-5. スレッドの待機」を参照して下さい。

次の例では、スレッド MyThread で、1 秒ごとにカウントを 1 ずつ増やしながらそれを出力します。

public class MyThread extends Thread
{
    private var _count:uint = 1;
    
    override protected function run():void
    {
        // カウントを表示
        trace(_count++);
    	
        // 1 秒待つ
        sleep(1000);
        // 再びこの実行関数を実行する
        next(run);
    }
    
    private function end():void
    {
        trace('End');
    }
}
var t:MyThread = new MyThread();
t.start();

4-3. イベント

event メソッドを呼び出すと、指定されたイベントが発生するまで、event メソッドを呼び出したスレッドを待機させ、 イベントが発生した際には、指定されたメソッドを実行関数として実行を再開させることが出来ます。 待機について詳しくは「2-5. スレッドの待機」を参照して下さい。

次の例では、スレッド MyThread で、Stage クラスのインスタンスに対してイベントハンドラを設定し、クリックした (MouseEvent.CLICK) 際には click メソッドを実行関数として、キーボードのキーを押した (KeyboardEvent.KEY_DOWN) 際には keyDown メソッドを実行関数として実行し、 それぞれ「click」と「keyDown」を出力します。

public class MyThread extends Thread
{
    public function MyThread(stage:Stage)
    {
    	_stage = stage;
    }
    
    private var _stage:Stage;
    
    override protected function run():void
    {
        event(_stage, MouseEvent.CLICK, click);
        event(_stage, KeyboardEvent.KEY_DOWN, keyDown);
    }
    
    private function click(e:MouseEvent):void
    {
        trace('click');
        next(run);
    }
    
    private function keyDown(e:KeyboardEvent):void
    {
        trace('keyDown');
        next(run);
    }
}
var t:MyThread = new MyThread(stage);
t.start();

event メソッドによって設定される実行関数は特殊で、通常のイベントハンドラと同様、発生したイベントを引数として取る必要があります。 その他の部分では通常の実行関数と違いは無く、全く同じ振る舞いをします。 ここでは click メソッドと keyDown メソッド共に、次の実行関数を run メソッドに設定しているため、これらのメソッドが実行された後、 run メソッドが実行され、再びイベント待ちをすることになります。


5. モニタ機構

スレッドの待機と再開を自分の手でコントロールしたい場合、モニタ機構を使用することが出来ます。これは特に、複数のスレッドの協調動作をさせたい場合に役立ちます。

モニタ機構は、IMonitor インターフェイスの実装クラスによって提供されます。ActionScript Thread Library には、 IMonitor インターフェイスの標準の実装である Monitor クラスが用意されており、更に Thread クラスMonitor クラスを継承しているので、このどちらかを使用するのが簡単です。

モニタ機構を簡単に説明すると、wait メソッドを呼び出したスレッドを、 notify メソッドもしくは notifyAll メソッドが呼び出されるまで待機させるものです。 待機について詳しくは「2-5. スレッドの待機」を参照して下さい。

あるモニタの wait メソッドを呼び出したスレッドは、そのモニタの「待機セット」と呼ばれる待合室に入り、 待機状態に移行します。そして、別の誰かがそのモニタの notify メソッド を呼び出すと、 待機セットに入っているスレッドのうち、ひとつが取り出され、実行が再開されます。 また、notifyAll メソッドを呼び出すと、待機セットに入っている全てのスレッドの実行が再開されます。

モニタ機構を使用すると、たとえば、あるリソースが利用可能な状態になるまで、スレッドを待機させる、といったことが可能になります。 次の例では、スレッド PrepareThread があるリソース Resource を準備し終えるまで、リソースの内容を出力する OutputThread を待機させます。

public class Resource extends Monitor
{
    // リソースの内容
    public var value:String = '';
}

public class PrepareThread extends Thread
{
    public function PrepareThread(resource:Resource)
    {
        _resource = resource;
    }
    
    private var _resource:Resource;
    
    override protected function run():void
    {
        // 数字をひとつ加える
        _resource.value += String(Math.floor(Math.random() * 10);
        
        // 5 桁になるまで繰り返す
        if (_resource.value.length < 5) {
            next(run);
        }
        else {
            // 5桁になったら準備完了なので
            // notifyAll で待機しているスレッドを起こす
            _resource.notifyAll();
        }
    }
}

public class OutputThread extends Thread
{
    public function OutputThread(resource:Resource)
    {
        _resource = resource;
    }
    
    private var _resource:Resource;
    
    override protected function run():void
    {
        // リソースの準備が完了 (notifyAll される) まで wait で待機する
        _resource.wait();
        // 次の実行関数
        next(output);
    }
    
    private function output():void
    {
        // リソースの内容を出力
        trace(_resource.value);
    }
}
var res:Resource = new Resource();
var prepare:PrepareThread = new PrepareThread(res);
var output:OutputThread = new OutputThread(res);
prepare.start();
output.start();

Resource クラスは Monitor クラスを継承しているため、モニタ機構としての役割も果たします。

OutputThread は、実行が開始されるとすぐに、Resource クラスの wait メソッドを呼び出し、待機状態に移行します。 また、PrepareThread は、実行が開始されると、リソースに対してランダムに数字を一桁ずつ、五桁になるまで付加していきます。 そして、五桁になると notifyAll メソッドを呼び出します。 この呼び出しによって、OutputThread の待機状態は解除され、次の実行関数として設定されていた output メソッドが実行され、 リソースの内容 (5桁のランダムな数字) が出力されます。

以上の例から分かるように、あるリソースの準備が完了するまで、そのリソースを利用するスレッドを待機させ、 準備が完了したら実行を再開する、といった流れが、モニタ機構を通して実現できました。 モニタ機構を使用すると、このような協調動作をさせることが簡単になります。


6. 割り込み機構

スレッドの動作をキャンセルしたい場合、割り込み機構を使用することが出来ます。割り込み機構は、スレッドのキャンセル処理を実装するための汎用的ないくつかのメソッドを提供します。

あるスレッドの interrupt メソッドを呼び出すと、そのスレッドに対して「割り込みフラグ」が設定されます。 割り込みフラグが設定されているかどうかは、isInterrupted プロパティをチェックすると分かります。 これが true を返す場合、割り込みフラグが設定されており、すなわち、そのスレッドの実行をキャンセルすることが要求されているということですので、 今行っている処理を終了し、スレッドが終了するようにすることが求められます。ただし、実際に何をするかはそのスレッドの実装者に委ねられており、 割り込みフラグが設定されているからといって、スレッドが勝手に終了するようなことはありません。

checkInterrupted メソッドを使用すると、割り込みフラグが設定されているかどうかを調べると同時に、 設定されていた場合は、その割り込みフラグを解除することが出来ます。

次の例では、数をカウントして出力する CountThread に対して、AnotherThread が開始から 5 秒後に割り込みを掛け、処理を終了するよう促します。

public class CountThread extends Thread
{
    private var _count:uint = 0;
    
    override protected function run():void
    {
        // カウントして出力
        trace(++_count);
        
        // 割り込まれていた場合終了
        if (checkInterrupted()) {
        	return;
        }
        
        // 繰り返す
        next(run);
    }
}

public class AnotherThread extends Thread
{
    public function AnotherThread(count:CountThread)
    {
        _count = count;
    }
    
    private var _count:CountThread;
    
    override protected function run():void
    {
        // 5 秒眠る
        sleep(5 * 1000);
        next(interruptCount);
    }
    
    private function interruptCount():void
    {
        // CountThread に対して割り込みを掛ける
        _count.interrupt();
    }
}
var count:CountThread = new CountThread();
var another:AnotherThread = new AnotherThread(count);
count.start();
another.start();

CountThread は run メソッドの実行によってカウントを出力し続けますが、5 秒後に行われる AnotherThread からの割り込みにより、割り込みフラグが設定され、 それまでは false を返していた checkInterrupted メソッドが、true を返すようになります。これにより次の実行関数が設定されるよりも前に実行関数が終了し、 スレッドが終了します。これは、AnotherThread からの要求によって、CountThread の実行がキャンセルされた、とみなすことが出来ます。

スレッドが待機状態の場合に割り込みを掛ける (interrupt メソッドを呼び出す) と、上記とは違う動作をします。 具体的には、interrupted メソッドによって実行関数が設定されていた場合は、待機状態を解除してその実行関数を実行し、 そうでない場合は、InterruptedError 例外を発生します。例外について詳しくは「7. 例外」で解説します。

次の例では、外部ファイルを読み込む URLLoaderThread を使って、そのファイルの内容を表示する OutputThread を実行中に、 ユーザーによってキャンセルボタンが押された場合、ファイルの内容を表示する代わりに「Interrupted!!」と出力して実行を終了します。

public class OutputThread extends Thread
{
    private var _loader:URLLoaderThread;
    
    override protected function run():void
    {
        // ファイルの読み込みを開始して終了を待つ
        _loader = new URLLoaderThread(new URLRequest('xxx.txt'));
        _loader.start();
        _loader.join();
        // 完了したら loadComplete へ
        next(loadComplete);
        // 割り込まれたら loadInterrupted へ
        interrupted(loadInterrupted);
    }
    
    private function loadComplete():void
    {
        // 読み込んだデータを表示して終了
        trace(_loader.loader.data);
    }
    
    private function loadInterrupted():void
    {
        // Interrupted!! を表示して終了
        trace('Interrupted!!');
    }
}
var output:OutputThread = new OutputThread();
output.start();

cancelButton.addEventListener(MouseEvent.CLICK, function(e:MouseEvent):void
{
    // キャンセルボタンがクリックされたら割り込みを掛ける
    output.interrupt();
});

OutputThread クラスの run メソッドの中で、next メソッドの他に interrupted メソッドを用いて、 待機中に割り込まれた場合に実行する実行関数 (ここでは loadInterrupted メソッド) を設定しています。 そのため、ファイルのロード中にキャンセルボタンがクリックされると、interrupt メソッドの呼び出しを通じて、 待機状態が解除され、loadInterrupted メソッドが実行関数として実行され、「Interrupted!!」の出力の後に終了します。

このように、割り込み機構を使用すると、柔軟なスレッドのキャンセル処理を行うことが出来ます。 組み込みで用意されているほとんどのスレッドは、きちんと割り込み処理に応答するようになっています。


7. 例外

スレッドの実行中 (正確に言うと実行関数の実行中) に例外が発生すると、その時点でスレッドの実行は中断され、例外ハンドラが登録されている場合、 そちらに処理が移行します。例外ハンドラが何事も無く終了され、例外ハンドラによって次の実行関数が設定された場合はその実行関数から処理が再開し、 次の実行関数が設定された無かった場合は、例外が発生する前に実行されていた実行関数から処理が再開します。

例外ハンドラが登録されていなかった場合、もしくは例外ハンドラの中で更に例外が発生した場合、 そのスレッドは finalize メソッド を経て実行を終了し、例外は親スレッドに伝播します。 このとき、親スレッドが待機状態であった場合は、待機状態が解除されます。親スレッドに伝播した例外も、同じ法則に沿って処理されます。 例外がトップレベルスレッドまで達し、もう伝播する親スレッドがいない場合、その例外は uncaughtErrorHandler 関数に引き渡されます。

例外ハンドラは、error メソッドによって登録することが出来ます。第一引数にはエラーの型を指定し、このタイプのエラーが発生した場合に、 第二引数のハンドラを実行するように出来ます。ハンドラは、発生した例外と、例外が発生したスレッドのふたつを引数として取る必要があります。

次の例では、URLLoaderThread による外部ファイルの読み込みが IOError によって失敗した場合に、読み込みをリトライします。

public class DataLoadThread extends Thread
{
    private var _loader:URLLoaderThread;
    
    override protected function run():void
    {
        // ファイルの読み込みを開始して終了を待つ
        _loader = new URLLoaderThread(new URLRequest('xxx.txt'));
        _loader.start();
        _loader.join();
        // 完了したら loadComplete へ
        next(loadComplete);
        // IOError が発生したら loadError へ
        error(IOError, loadError);
    }
    
    private function loadError(e:IOError, t:Thread):void
    {
        // 再読み込みする
        run();
    }
    
    private function loadComplete():void
    {
        // 読み込んだデータを表示して終了
        trace(_loader.loader.data);
    }
}
var dataLoad:DataLoadThread = new DataLoadThread();
dataLoad.start();

8. 動作チャート

スレッド動作チャート

ここまでに解説したスレッドの動作を視覚的にまとめたチャートを製作しました (クリックで拡大します) 。ぜひ、スレッドの理解に役立てたり、印刷して部屋に飾り、インテリアとして楽しんだりしてください。


9. ユーティリティ (org.libspark.thread.utils)

9-1. SerialExecutor

SerialExecutor は複数のスレッドを順番に実行するためのユーティリティクラスです。

開始したスレッドの終了を待って次のスレッドを実行し、全てのスレッドの実行が終了するとこのスレッドも終了します。

このスレッドに対して割り込みがかけられた場合、その時点で実行されているスレッドに対して同じように割り込みを掛けた上で そのスレッドの終了を待ち、それ以降のスレッドの実行をキャンセルします。

実行中のスレッドで例外が発生した場合、このスレッドは特に何もせず、例外を親に伝播させます。

9-2. ParallelExecutor

ParallelExecutor は複数のスレッドを並列して実行するためのユーティリティクラスです。

同時に全てのスレッドを開始し、全てのスレッドの実行が終了するとこのスレッドも終了します。

このスレッドに対して割り込みがかけられた場合、追加されている全てのスレッドに対して同じように割り込みを掛けた上で 全てのスレッドの終了を待ちます。

実行中のスレッドで例外が発生した場合、このスレッドは特に何もせず、例外を親に伝播させます。

9-3. EventDispatcherThread

EventDispatcherThread は IEventDispatcher インターフェイスを実装したスレッドです。

9-4. 進捗通知機構

進捗通知機構は、主にデータをロードするスレッドにおいて、ある時点でデータがどれぐらいロードされているかを知るため (更に具体的に言うとプログレスバーを作るため) に用意されている、 汎用的な一連のクラス郡です。

進捗を通知可能なスレッドは、IProgressNotifier インターフェイスを実装しており、IProgress インターフェイスの実装クラスをプロパティとして返します。

IProgress インターフェイスは、進捗状況を表現し、 仕事量の合計 (total プロパティ) と現在までに完了している仕事量 (current プロパティ)、 それに伴う割合 (percent プロパティ)、仕事が失敗したか (isFailed プロパティ)、キャンセルされたか (isCanceled プロパティ) 等を、 プロパティとして提供します。さらに、これらのプロパティが変更されるとき、ProgressEvent クラスで定義される各種イベントを発行します。

Progress クラスは、この IProgress インターフェイスの最も一般的な実装クラスで、 新たに IProgressNotifier インターフェイスを実装するクラスを作成する場合に使用することが出来ます。

MultiProgress クラスは、複数の進捗状況をひとつにまとめます。addProgress メソッドで、進捗状況を追加することができます。


10. 様々なスレッド (org.libspark.thread.threads)

10-1. URLLoaderThread

URLLoaderThread は、URLLoader を用いてデータを読み込むためのスレッドです。

このスレッドを開始すると、与えられた URLRequest を用いてロード処理を開始し、 ロードが完了 (Event.COMPLETE) するとスレッドが終了します。

join メソッドを用いると、簡単にロード待ちをすることができます。

ロード中にエラーが発生した場合は、以下の例外がスローされます。 これらの例外は、このスレッドを開始したスレッド(親スレッド)で捕捉する事が出来ます。

10-2. LoaderThread

LoaderThread は、Loader を用いてファイルを読み込むためのスレッドです。

このスレッドを開始すると、与えられた URLRequest と LoaderContext を用いてロード処理を開始し、 ロードが完了 (Event.COMPLETE) するとスレッドが終了します。

join メソッドを用いると、簡単にロード待ちをすることが出来ます。

ロード中にエラーが発生した場合は、以下の例外がスローされます。 これからの例外は、このスレッドを開始したスレッド (親スレッド) で捕捉することができます。

10-3. SoundLoaderThread

SoundLoaderThread は、Sound を読み込むためのスレッドです。

このスレッドを開始すると、与えられた URLRequest と SoundLoaderContext を用いてロード処理を開始し、 ロードが完了 (Event.COMPLETE) するとスレッドが終了します。

join メソッドを用いると、簡単にロード待ちをすることが出来ます。

ロード中にエラーが発生した場合は、以下の例外がスローされます。 これからの例外は、このスレッドを開始したスレッド (親スレッド) で捕捉することができます。

10-4. TweenerThread

TweenerThread は、Tweener を実行するためのスレッドです。使用するには別途 Tweener が必要です。

スレッドが開始されると、コンストラクタで指定されたターゲットと引数を用いて Tweener の実行を開始し、 トゥイーンが終了するとスレッドの実行も終了します。

スペシャルプロパティとして、以下のプロパティが拡張されています。


11. スレッドのサンプル

スレッドのサンプルは、ActionScript Thread Library プロジェクトページの「ダウンロード」へ行き、「Thread-1.0-src.zip」をダウンロードするか、 或は最、Subversion 経由で以下のコマンドを利用してリポジトリから直接取得することで入手出来ます。

$ svn co http://www.libspark.org/svn/as3/Thread/tags/v1.0/samples Thread-samples

TweenerThread を含むサンプルを実行するには、別途 Tweener をダウンロードしてクラスパスを通す必要があります。

Frocessing2DThread または Frocessing3DThread を含むサンプルを実行するには、別途 Frocessing をダウンロードしてクラスパスを通す必要があります。

11-1. samples/hello

「Hello,」「Thread」「!!」を順に出力するスレッドのサンプルです。

11-2. samples/tweener

TweenerThread を用いて、四角いシェイプをアニメーションさせるサンプル二種類 (Sample.as と Sample2.as) です。

11-3. samples/urlloader

URLLoaderThreadParallelExecutor を用いて、三つの URL から並行してデータをダウンロードして出力するサンプル (Sample.as) と、 URLLoaderThreadIProgress インターフェイスを用いて、進捗状況も同時に表示するサンプル (SampleWithProgress.as) です。

11-4. samples/progress

IProgress インターフェイスMultiProgress クラスを用いて、複数の仕事の進捗状況をプログレスバートして表示するサンプルです。

11-5. samples/loadImages

URLLoaderThreadLoaderThread を用いて、外部 XML ファイルで指定された画像をロードして表示するサンプルです (wirtten by minaco)。

11-6. samples/tweenAndEvent1

TweenerThreadevent メソッドを用いて、パンダが首を、右、左に傾け動き、パンダをクリックすると、5秒停止し再び動きだすサンプルです (written by minaco)。

11-7. samples/tweenAndEvent2

TweenerThreadevent メソッドを用いて、パンダが首を、右、左に傾け動き、パンダをマウスオーバーすると停止し、マウスアウトすると再び動きだすサンプルです (written by minaco)。

11-8. samples/frocessing

Frocessing3DThread を用いて、Frocessing でグラフィクスを描画するサンプルです。

11-9. samples/flickrsphere

ActionScript Thread Library の機能をまんべんなく使用し、ひとつの完結したコンテンツとして作成したサンプル「FlickrSphere」です。 Flickr の写真を、色に基づいて球体上にマッピングして表示します。

こちらから実際に動く「FlickrSphere」を閲覧可能です。

12. スレッドのパターン

かきかけです。ごめんなさい


13. スレッドのヒント

かきかけです。ごめんなさい

14. クレジット