並行処理の奥義、非同期フレームワークとは?:Symbian OS開発の勘所(6)(3/3 ページ)
Symbian OSでは「アクティブオブジェクト」という非同期処理の機構を用いて、マルチタスク環境におけるシステムの応答性を確保している
アクティブオブジェクト−非同期要求を抽象化する仕組み
Symbian OSにおいては、「漏れなく」という要求は「フレームワークを用意する」という方法で解決するのが王道です。第4回にも書いた、間違える自由を選ばせないというSymbian OSのスタイルは、非同期要求においても例外ではありません。
個々の非同期要求を扱うため、Symbian OSでは「アクティブオブジェクト」という機構が導入されています。
class CActive : public CBase { public: IMPORT_C void Cancel(); // 未解決の要求のキャンセル IMPORT_C void Deque(); IMPORT_C void SetPriority(TInt aPriority); inline TInt Priority() const; inline TBool IsActive() const; protected: CActive(TInt aPriority); void SetActive(); virtual void DoCancel() =0; // キャンセル中核処理 virtual void RunL() =0; // 要求完了時処理 virtual TInt RunError(TInt aError); public: TRequestStatus iStatus; };
アクティブオブジェクトの定義 |
この基底クラスは非同期要求を管理する場合に必要なインターフェイスを定義するものです。具体的な非同期要求を扱う場合には(ネットワークへのI/O、キーボードからの文字入力、ボタンの押下、など)、それぞれを管理するアクティブオブジェクトの派生クラスを用意し、そのインスタンスを通じて非同期要求の管理を行います。
Symbian OSにおける非同期要求操作は、
- 要求発行
- 完了通知の受領
- キャンセル
これら3点からなります。それぞれのシーケンスを図示してみましょう。
図5 非同期要求操作のシーケンス図。※キャンセル要求の受付時にはサービスプロバイダでいろいろなコードを実行します。そのため呼び出し側は、キャンセル要求自体の受付結果を拾わなければ先に進めません。ただしこの完了待ちはキャンセル要求用のAPIの中でシングルウェイトを行うことが一般的であり、呼び出し側で意識する必要はないはずです。え、そうなってない? そのAPIをデザインした担当者とじっくり話をする必要がありそうです……
要求発行は図5中(A)の部分の処理になります。ここに対してCActiveではインターフェイスの定義をしません。というのは、[サービスプロバイダ]が何であるかによって要求の出し方が異なるからです。要求発行はアクティブオブジェクトの派生クラスが任意に定めます。
完了通知の受領はRunL()というメンバ関数で行います。図5中(B)の部分の処理です。CActiveではインターフェイスのみを定義し、実際にメンバ関数を用意するのは派生クラスの責務になります(純粋仮想関数になっていますね)。
キャンセルはちょっと複雑で、キャンセル要求の発行(図5中(C))と、その完了待ち(図5中(D))の2パートから成っています。キャンセル要求の発行であるDoCancel()はCActiveではインターフェイスのみの定義で、派生クラスが実装の責任を持ちます。キャンセルの完了待ちは[サービスプロバイダ]に依存しないので、CActiveクラスに実装を集約します。
CActive::Cancel() { if ( IsActive() ) { DoCancel(); // キャンセル要求の発行 // 派生クラスの実装が呼ばれる User::WaitForRequest(iStatus); // (A)で行われた要求のキャンセル完了待ち // 十分に短い時間であるという前提で、 // シングルウェイトを行う } }
キャンセル完了待ちのイメージ |
例としてキー入力を受け取るためのアクティブオブジェクト派生クラス、CActiveConsoleを考えてみます。
図6のクラス図を翻訳すると、以下のようなクラス定義になります。
class CActiveConsole : public CActive { protected: CConsoleBase* iConsole; // コンソールへのポインタ // これがサービスプロバイダとなる public: CActiveConsole( CConsoleBase& aConsole ); ~CActiveConsole(); void RunL(); // 完了通知の受領処理 void DoCancel(); // キャンセル要求発行処理 void RequestCharacter(); // サービスプロバイダへの要求発行処理 private: void ConstructL(); };
CActiveConsoleの定義 |
イメージがわきにくいと思うので、RequestCharacter()とRunL()の例を以下に示します。
結局のところ、CAvtiveConsole::RunL()はキー押下時のメッセージハンドラなのです。要求発行のためにCAvtiveConsole::RequestCharacter()を出すところが見慣れない部分ですが、それ以外はGUI環境で一般的なハンドラスタイルのコードです。非同期処理を行うために必要な処理を基底クラスに隠ぺいし、実際の非同期処理側ではやりたいことにフォーカスしたプログラムを書ける。これがSymbian OS的な非同期処理デザインです。
Symbian OSではこのように非同期処理を安全に書くためのフレームワークが用意されています。非同期処理が手軽に書けるということは、性能の良い並行処理を実現しやすいということです。一見面倒に見えますが、しかし同じ品質のノンブロッキングコードを誰もが書ける(というか、そうとしか書けない)のは、応答性が要求される環境にとって重要なポイントです。
では誰がRunL()を呼び出すのでしょうか。しかし紙数が尽きました。次回はアクティブオブジェクトを使ってイベントをウェイトするための仕掛け−アクティブスケジューラの解説と、Symbian OSの非同期処理に対する典型的な誤解の解消、そして最終的にこの非同期フレームワークがいかに省電力に資するのかという解説までたどり着きたいと思います。(次回に続く)
Copyright © ITmedia, Inc. All Rights Reserved.