28 September 2008
ページ ツール |
すべて
はじめにこの処理をして、次にこの処理をして...、という処理の流れを「スレッド (Thread)」と呼びます。英単語の「Thread」には「糸」「縫い糸」「筋道」「脈絡」といった意味があります。
ActionScript 3.0 (FlashPlayer 9) は、処理の流れがひとつしかない「シングルスレッド」で、かつイベントを介して処理を進める「イベント駆動」を採用しているため、データのロード完了を待つ、ユーザーがマウスをクリックするのを待つ、といった非同期処理が入るととたんにコードが複雑になる傾向があります。 そこで登場するのが ActionScript Thread Library 1.0 (そうめん) です。
ActionScript Thread Library は、タスクシステムと Java のスレッドモデルをベースとした「疑似スレッド」を実現するライブラリで、複雑で冗長になりがちな、イベント処理や非同期処理、リアルタイム処理を、分かりやすくスマートに記述することを可能にします。
最近の Flash や Flex では、外部データのやり取りをしない方が少ないと思います。特に、XML や、外部 SWF の読み込みは、必ずと言っていいほど行われます。
たとえば、複数の XML を順番に読み込んで、全ての読み込みが終わったら何か処理をする、という例を、ActionScript Thread Library を使って書くと、次のようになります。
import org.libspark.thread.Thread;
import org.libspark.thread.threads.net.URLLoaderThread;
import org.libspark.thread.utils.SerialExecutor;
public class LoadXMLsThread extends Thread
{
private var _loaders:SerialExecutor;
override protected function run():void
{
// みっつの XML を読み込む
_loaders = new SerialExecutor();
_loaders.addThread(new URLLoaderThread(new URLRequest('a.xml'));
_loaders.addThread(new URLLoaderThread(new URLRequest('b.xml'));
_loaders.addThread(new URLLoaderThread(new URLRequest('c.xml'));
_loaders.start();
_loaders.join();
next(loadComplete);
}
private function loadComplete():void
{
// 何か処理をする... ここでは XML の内容を出力
for (var i:uint = 0; i < _loaders.numThreads; ++i) {
trace(URLLoaderThread(_loaders.getThreadAt(i)).loader.data);
}
}
}
ここで、三つの新しいクラスが登場しています。
Thread クラスの start メソッドを呼び出すと、スレッドの実行が開始されます。
_loaders.start();
ここでは、SerialExecutor の実行が開始され、addThread されている、「a.xml」と「b.xml」と「c.xml」を読みにいく URLoaderThread が、順番に実行されます。
Thread クラスの join メソッドを呼び出すと、呼び出した先のスレッドの終了を待機して、next メソッドで指定したメソッドを終了後に呼び出すようになります。
_loaders.join();
next(loadComplete);
そのため、SerialExecutor の実行が終了 (= 全ての URLLoaderThread の読み込みが終了) すると、loadComplete メソッドが呼び出され、取得した XML の内容が出力されます。
実際の開発では、読み込みの処理は更に複雑になることがあります。例えば、まず XML を読んで、その内容を元に、画像ファイルを読み込んで表示する、といった処理です。
これを ActionScript Thread Library を使って書いてみると次のようになります。
import org.libspark.thread.Thread;
import org.libspark.thread.threads.net.URLLoaderThread;
import org.libspark.thread.threads.display.LoaderThread;
import org.libspark.thread.utils.SerialExecutor;
public class LoadXMLandImageThread extends Thread
{
private var _loader:URLLoaderThread;
private var _loaders:SerialExecutor;
override protected function run():void
{
// XML を読み込む
_loader = new URLLoaderThread('images.xml');
_loader.start();
_loader.join();
next(loadXMLComplete);
}
private function loadXMLComplete():void
{
// 読み込んだ XML から更に画像を読み込むスレッドを作成して実行する
_loaders = new SerialExecutor();
var xml:XML = XML(_loader.loader.data);
for each (var item:XML in xml.item) {
_loaders.addThread(new LoaderThread(new URLRequest(String(item))));
}
_loaders.start();
_loaders.join();
next(loadImagesComplete);
}
private funciton loadImagesComplete():void
{
// 表示処理など...
}
}
ここで、次のクラスが新しく登場しています。
はじめに、URLLoaderThread を用いて「image.xml」を読み込んでいます。読み込みが完了すると、next メソッドによって指定された loadXMLComplete メソッドが呼び出されます。
loadXMLComplete メソッドでは、読み込んだ XML の item 要素を取得し、そこに書かれているファイル名の画像を読み込む LoaderThread を作成して、SerialExecutor に追加しています。
たとえば、images.xml が次のような内容であった場合、
<items>
<item>a.jpg</item>
<item>b.jpg</item>
<item>c.jpg</item>
</items>
「a.jpg」「b.jpg」「c.jpg」がそれぞれ LoaderThread によって読み込まれます。読み込みが完了すると、next メソッドによって指定された loadImagesComplete メソッドが呼び出されます。
このように、ActionScript Thread Library では、Thread の start メソッド、join メソッド、next メソッドなどを組み合わせて、処理を柔軟に記述することが出来ます。
エラーが発生した場合に、それをそのまま放っておくのは不親切です。エラーメッセージを表示する、リトライするなど、何か処理を行いたい場合がほとんどだと思います。
URLLoaderThread や、LoaderThread は、エラー (内部的にはエラーイベント) が発生した場合に、それを例外としてスローします。スローされた例外は、親スレッド (そのスレッドを開始したスレッド) に伝播していくようになっています。そのようにして伝播してきた例外は、error メソッドで捕捉することができます。
import org.libspark.thread.Thread;
import org.libspark.thread.threads.net.URLLoaderThread;
import flash.errors.IOError;
public class LoadXMLandImageThread extends Thread
{
private var _loader:URLLoaderThread;
override protected function run():void
{
_loader = new URLLoaderThread('data.xml');
_loader.start();
_loader.join();
next(loadComplete);
error(IOError, loadError);
error(SecurityError, loadError);
}
private function loadComplete():void
{
trace(_loader.loader.data);
}
private funciton loadError(e:Error, t:Thread):void
{
trace('読み込みエラーが発生しました');
next(null);
}
}
error メソッドでは、第一引数ひ捕捉したい例外の種類、第二引数に例外発生時に呼び出すメソッドを指定します。この例では、IOError (内部的には IOError イベント) かSecurityError (内部的には SecurityError イベント) が発生した際に、loadError メソッドが呼び出されます。エラーが発生しなかった場合、loadComplete が呼び出されます。
例外発生時に呼び出されるメソッドは、第一引数に発生した例外、第二引数に例外が発生したスレッドを採るようにします。このメソッドが何事も無く終了すると、例外発生前に実行していたメソッドに復帰します。ここでは、next メソッドに null を指定することで、なにもせずスレッドが終了するようにしています。
ActionScript Thread Library では、このように、try-catch ライクに例外処理をすることが出来ます。
これまでデータの読み込みを例に見てきましたが、もちろん、アニメーションにも使用することが出来ます。
次の例では、1秒ごとに、ランダムな位置に Sprite をアニメーションさせながら移動させます。
import org.libspark.thread.Thread;
import org.libspark.thread.threads.tweener.TweenerThread;
import flash.display.Sprite;
public class AnimationThread extends Thread
{
public function AnimationThread(sprite:Sprite)
{
_sprite = sprite;
}
private var _sprite:Sprite;
override protected function run():void
{
var tweener:TweenerThread = new TweenerThread(_sprite, {
time: 0.5,
x: Math.random() * _sprite.stage.stageWidth,
y: Math.random() * _sprite.stage.stageHeight,
transition: 'easeOutCubic'
});
tweener.start();
tweener.join();
next(tweenComplete);
}
private function tweenComplete():void
{
sleep(1000);
next(run);
}
}
ここで、次のクラスが新しく登場しています。
スレッドの実行が開始されると、コンストラクタで指定された Sprite に対して、TweenerThread を用いて、ランダムな座標に 0.5 秒かけて移動するアニメーションを実行します。
アニメーションが終了すると、next メソッドで指定された tweenComplete メソッドが呼び出されます。
tweenComplete メソッドでは、sleep メソッドを呼び出して、1000 ミリ秒後 (1 秒後) に、next メソッドで指定した run メソッドが実行されるようにしています。
run メソッドが実行されるということは、再び TweenerThread が実行され、アニメーションをし、また 1 秒待ち、という風に、処理がループするようになります。
既に行われている動作に割り込んで、別の動作を行う、すなわちキャンセル処理を行いたい、というのも、実際の開発ではよくあることです。
ActionScript Thread Library では、スレッドに対するキャンセル機能を実現する、汎用的な割り込み機構を提供しています。
次の例では、さきほどのランダムな座標にアニメーションするスレッドに対して、キャンセル (割り込み) が要求された場合は、Sprite をフェードアウトして消すという処理を行います。
import org.libspark.thread.Thread;
import org.libspark.thread.threads.tweener.TweenerThread;
import flash.display.Sprite;
public class AnimationThread extends Thread
{
public function AnimationThread(sprite:Sprite)
{
_sprite = sprite;
}
private var _sprite:Sprite;
override protected function run():void
{
var tweener:TweenerThread = new TweenerThread(_sprite, {
time: 0.5,
x: Math.random() * _sprite.stage.stageWidth,
y: Math.random() * _sprite.stage.stageHeight,
transition: 'easeOutCubic'
});
tweener.start();
tweener.join();
next(tweenComplete);
// 割り込まれた場合
interrupted(tweenInterrupted);
}
private function tweenComplete():void
{
sleep(1000);
next(run);
// 割り込まれた場合
interrupted(tweenInterrupted);
}
private function tweenInterrupted():void
{
var tweener:TweenerThread = new TweenerThread(_sprite, {
time: 0.5,
alpha: 0
});
tweener.start();
tweener.join();
next(tweenInterruptedComplete);
}
private function tweenInterruptedComplete():void
{
_sprite.parent.removeChild(_sprite);
}
}
interrupted メソッドを用いると、割り込み要求がされた場合に実行されるメソッドを指定出来ます。
ここでは、tweenInterrupted メソッドを指定しているため、新たに TweenerThread によってアルファが 0 になるアニメーションが実行され、そのあとで、next メソッドで指定されている tweenInterruptedComplete メソッドが呼び出され、表示オブジェクトツリーから削除されます。
実際に割り込み要求を出すには、実行中のこの AnimationThread のインスタンスに対して、interrupt メソッドを呼び出します。
animationThread.interrupt();
こうすると、割り込みフラグが設定され、通常の動作から、割り込み動作に移行します。
ActionScript Thread Library 1.0 (そうめん) を使ううえでの最大のメリットは、非同期処理を含む、全てのコントローラ的な処理が、このスレッドのスタイルで統一して記述できるところにあります。そのおかげで、コードが読み易くすっきりし、開発スピードも上がりますし、処理の順番の入れ替え等が簡単なため、開発途中での仕様変更にも強くなります。
開発者の僕自身、既にいくつかの仕事で使っており、ActionScript Thread Library なしには生きていけない体になっています。
仕事レベルの複雑な処理ほど威力を発揮するので、是非皆さんも使ってみて下さい。
詳細や、ダウンロード、サンプルや制作事例等は以下のページで見ることが出来ます。
ドキュメントは以下にありますので、是非一度目を通してみて下さい。