ActionScript Worker (Flash Player マルチスレッド) の基本的な使い方

Flash Player 11.4 のベータ版公開 (関連記事) に伴い、ベータ用の ASDoc が更新されました。マルチスレッド関連のクラスの仕様も、具体的に記述されています。

(ご参考までに、ActionScript Worker の概要は以前の記事でも紹介しています)

とはいえ、いきなり API を眺めるよりは、具体的な使い方から始めた方が楽だと思いますので、今回は、ベータ版と一緒に提供されている Worker のサンプルから、HelloWold の内容を解説します。

読み進める前に、以下のファイルをダウンロードして、中に含まれる HelloWorld.zip を展開して下さい。

なお、今回のベータで利用できる ActionScript Worker は、デスクトップ環境での利用を前提としたもので、デバイス環境 (AIR) からは利用できません。

また、デバッグを含め、Worker の機能をフルにサポートする開発環境は、次バージョンの Flash Builder であるということが予告されています。次バージョンの Flash Builder は 8 月にベータ版が公開される予定だそうです。

始めに

ActionScript Worker では、最初に生成される Worker を primordial worker と呼びます。従来のシングルスレッドの環境は、primordial worker だけの状態だったと理解してよいようです。

それ以外の Worker は、primordial worker から、または更にその派生として、動的に生成します。生成された Worker は SWF ファイルを読み込み、それを実行します。

生成された Woker の内部では、使用できる API が制限されたり、一部動作の異なる API があります。つまり、従来の SWF がそのまま実行できるのは primordial worker のみです。

Worker 間では、属性を共有する、あるいは、通信用のチャネルを使用する、等の情報共有のための手段があります。

HelloWorld サンプルの基本構造

以下の説明は、説明を簡略化するため、元のサンプルコードから多少変更してあります。その点ご了承の上お読み下さい。

さて、まず、HelloWorld クラスは以下のような構造をしています。if 文を使って、自身が primordial かどうかを判断して、ロジックを振り分けています。

この記述方法を採ると、1 つのソースコードだけで、親と子の worker それぞれのロジックを記述できるので便利です。特に、開発環境が、子 Worker 用の SWF ファイル管理機能を提供しない現状では、効率的な方法だと思われます。

ただし、この様な記述をすると、Worker ごとに専用の SWF ファイルを用意するのと比べて、各 Worker に余分なリソースを読み込むことになります。なので、使う場合は選んだ方が良いかもしれません.

// コンストラクタ
public function HelloWorld()
{
  if (Worker.current.isPrimordial)
  {
    // ここに"primordial worker用"のロジックを記述
  }
  else
  {
    // ここに"子worker用"のロジックを記述
  }
}

Worker.current は、スクリプトを実行している Worker のインスタンスを指します。Worker.isPrimordial が true なら、その Worker は最初に生成されたメイン Worker であることを意味します。

子 Worker の生成

Worker の生成には、WorkerDomain.current.createWorker() メソッドを使います。引数には SWF の格納された ByteArray を指定します。

今回のサンプルでは、子の Worker も、親と同じ SWF ファイルを実行します。そのため、loaderInfo からバイトデータを取得して、Worker を生成します。

private var worker:Worker = null;
 
public function HelloWorld()
{
  // 実行中のSWFファイルのバイトデータを取得する
  var myByteArray:ByteArray = new ByteArray();
  myByteArray = this.loaderInfo.bytes;
 
  if (Worker.current.isPrimordial) 	
  {
    myWorker = WorkerDomain.current.createWorker(myByteArray);
    myWorker.start();
  }
  else {}
}

生成した Worker は start() メソッドを呼ぶまで実行されません。

Worker のインスタンスは、一旦実行されると、自身で System.exit() を呼ぶか、他の Worker から terminate() を呼ばれるか、例外が発生すると終了します。

さて、ここまでで 2 つの Worker を実行できるようになりました。次は、Worker 間での通信の実現です。

MessageChannel の作成

Worker 間の通信の主要な手段は MessageChannel です。Worker の createMessageChannel() メソッドでインスタンスを生成します。引数は通信先の Worker です。

MessageChannel は一方向の通信チャネルのため、双方向のコミュニケーションを行うには 2 つのインスタンスが必要になります。下のような感じです。

private var fromMain:MessageChannel;
private var fromChild:MessageChannel;
 
public function HelloWorld()
{
  if (Worker.current.isPrimordial)
  {
    // 子Workerを生成後にメッセージチャネルを生成する
    ...
    // primordial workerから子workerへの送信チャネル
    fromMain = Worker.current.createMessageChannel(myWorker);
 
    // 子workerからprimordial workerへの送信チャネル
    fromChild = myWorker.createMessageChannel(Worker.current);
    ...
  }
  else {}
}

メッセージを受信すると、MessageChannel のインスタンスに Event.CHANNEL_MESSAGE イベントが発生します。これを処理できるよう、受信用のチャネルにはイベントハンドラを追加します。

if (Worker.current.isPrimordial)
{
  ...
  fromChild = myWorker.createMessageChannel(Worker.current);
  fromChild.addEventListener(Event.CHANNEL_MESSAGE, onChildMessage);
  ...
}
 
private function onChildMessage(evt:Event):void
{
  if (evt.target.messageAvailable == true)
  {
    // 子Workerからのメッセージを受信する
    var message:String = evt.target.receive();
  }
}

イベントの target は MessageChannel のインスタンスです。その messageAvailable 属性の値が ture なら、receive() を使ってデータを取得できます。データの型は * です。

子 Worker 側でも同様の設定をします。当然、イベントハンドラを追加するコードは、子 Worker 内で実行されるように記述しなければなりません。

if (Worker.current.isPrimordial)
{
  // 以下はprimordial worker内で実行される
  fromMain = Worker.current.createMessageChannel(myWorker);
}
else
{
  // 以下は子Worker内で実行される
  fromMain.addEventListener(Event.CHANNEL_MESSAGE, onMainMessage);
}
 
private function onMainMessage(evt:Event):void
{
  if (evt.target.messageAvailable == true)
  {
    // primordial workerからのメッセージを受信する
    var message:String = evt.target.receive();
  }
}

しかし、上のコードには問題があります。

それは MessageChannel の生成コードが primordial worker 内で実行されているという点です。そのため、primodial worker 内では、fromMain 変数に生成されたインスタンスへの参照が設定されています。これは当然ですね。

しかし、子 Worker では、fromMain に参照先を設定するコードが実行 (そもそも記述も) されていません。参照先が分からなければ、イベントハンドラも追加できません。

また、他の Worker の属性値を直接覗くことはできません。そのため、動的に生成されたリーソスを共有するには一手間が必要になります。

Worker 間でのオブジェクトの共有

Worker には、特定の Woker に対して、共有する属性を設定することができる setSharedProperty() メソッドがあります。最初の引数にキーとなる文字列、次の引数に任意のオブジェクトへの参照を渡します。

下の例では、primordial worker から、子 Worker に対して、2 つの MessageChannel への参照を共有しています。

if (Worker.current.isPrimordial)
{
  // 以下はprimordial worker内で実行される
  myWorker.setSharedProperty("fromMain", fromMain);
  myWorker.setSharedProperty("fromChild", fromChild);
}
else
{
  // 以下は子Worker内で実行される
  fromMain = Worker.current.getSharedProperty("fromMain");
  fromChild = Worker.current.getSharedProperty("fromChild");
}

受け取る側の Worker は getSharedProperty() メソッドを使います。引数に適当なキーを指定すると、対応するオブジェクトへの参照が取得できます。

ところで、上の例では、子 Worker が自身への参照を取得するために、Worker.current を使用してる点にも注意して下さい。myWorker も、子 Worker 側では利用できません。

ここまでで、通信用のチャネルとイベントハンドラの設定が完了しました。あとは、実際にメッセージの送信を行うだけですが...

Worker のステータス

子 Worker に対して start() を呼んでも、直後に動作し始める保証はありません。そして、実際に動作が開始されるまで、primordial worker は子 Worker と連携処理を始めることができません。

このような要件に対応すべく、Worker の状態を通知できるよう、新しく Event.WORKER_STATE イベントが追加されました。

イベントハンドラの利用方法は、通常と同じです。ただし、ハンドラの実行される Worker が、ハンドラを追加した Worker (下の例では primordial worker) であることには注意が必要です。

if (Worker.current.isPrimordial)
{
  myWorker = WorkerDomain.current.createWorker(myByteArray);
  myWorker.addEventListener(Event.WORKER_STATE, onWorkerEvent);
  ...
  myWorker.start();
}
 
private function onWorkerEvent(evt:Event):void
{
  if (evt.target.state == WorkerState.RUNNING)
  {
    // 子Workerが実行中、メッセージ送信も可能
    fromMain.send("こちらはメインスレッドです");
  }
}

イベントが発行されるのは Worker の状態が変わった時です。通知される状態は以下の 3 種類です。

  • NEW:新しく Woker が生成された
  • RUNNNING:Worker の実行が開始された
  • TERMINATED:terminate() により実行が中断された

これらの値は、イベントの target から参照される woker インスタンスの state 属性から取得します。

ステータスが RUNNING であれば、メッセージを送信できます。MessageChannel を使った送信には send() メソッドを使います。

あとは、それぞれの MessageChannel のイベントハンドラに適当な処理を記述します。

private function onMainMessage(evt:Event):void
{
  if (evt.target.messageAvailable == true)
  {
    // primordial workerからのメッセージを受信する
    var message:String = evt.target.receive();
    trace(message);
    // primordial workerにメッセージを送信
    fromChild.send("こちらはサブスレッドです");
  }
}
 
private function onChildMessage(evt:Event):void
{
  if (evt.target.messageAvailable == true)
  {
    // 子Workerからのメッセージを受信する
    var message:String = evt.target.receive();
    trace(message);
  }
}

以上で、primordial worker から子 Worker に 「こちらはメインスレッドです」 を送り、その返事として子 Worker から primordial worker に 「こちらはサブスレッドです」 を送る処理が完成しました。

ちょっと長かったですね。

 

トラックバック(0)

トラックバックURL: http://cuaoar.jp/mt4/mt-tb.cgi/333

コメント(4)

なんか前回のMAXで公開された機能が無い気がするのですが…

例えば、SWF を読み込む(FPの別インスタンスを作成する)以外にも
スレッドを作成する手段(API)があったと思うのですが?

ひょっとして、2011-11-06事件によってプロジェクトが縮小され
スレッド機能も簡素化されたのでしょうか?

それとも、今はセキュリティ的な課題があって
「FPの別インスタンスを作成する」だけになってしまったのでしょうか?

ActionScriptWorkerをFlex(Spark framework)で試したみたのですが、
正常ビルド、起動はしますが、マルチスレッドが機能していないのですが
Flexには未対応なのでしょうか?

Shigeru Nakagaki さん、こんにちは。

公開されていない製品戦略の話は私には分からないのです。すみません。

MarbayClip さん、こんにちは。

原理的には、primordial worker では Flex フレームワークは使えるように思います。
現状の Flex SDK のままで動作するなら、そのうちアドビから何らかの情報が公開されると思います。
もしかすると 8/2 日の FxUG あたりで関連する話が聞けるかも?
SDK の修正が必要な場合は、Apache からの発表を待つことになるのかもしれませんね。

コメントする

2014年1月

Sun Mon Tue Wed Thu Fri Sat
      1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31  
レンタルサーバー

月別 アーカイブ

Powered by Movable Type 4.261