連載:次世代技術につながるSilverlight入門 避けて通れない「非同期処理」を克服しよう 岩永 信之2012/08/02 |
|
|
■非同期APIのパターン
具体的なサンプル・コードを使った説明に入る前に、注意しておく点がある。歴史的経緯から、.NETとSilverlightの非同期APIの書き方には、以下の3つの異なるパターンが存在する。
- APM(Asynchronous Programming Model): Begin/Endのメソッドのペアを使う
- EAP(Event-based Asynchronous Pattern): 開始用のメソッドと、結果受け取り用のイベントのペアを使う
- TAP(Task-based Asynchronous Pattern): Taskクラスのオブジェクトを返す1つのメソッドを使う
Table 1にそれぞれの例(C#)を示す。
API定義 | API利用 | |
同期 | U F(T x); |
Before(); |
APM | IAsyncResult BeginF(T x, |
Before(); |
EAP | class FArgs : EventArgs |
Before(); |
TAP | Task<U> FAsync(T x); |
Before(); |
TAP (C# 5.0) |
Before(); |
|
Table1: .NETの非同期APIパターン(C#) |
APMは、基本形ともいえるもので、オーバーヘッドの少ないパターンである。しかし、マイクロソフトがユーザビリティ・テストを行った結果、正しく使える人が少なかったそうで、直接の利用は避けられる傾向にある。
EAPは、.NET 2.0〜3.5のころによく使われたパターンである。イベントを使っているため、Visual Studioの補助を受けやすい半面、開始(=語尾が「Async」のメソッド呼び出し)と結果の受け取り(=Completedイベントのハンドラ登録)が逆順になり、コードが追いづらいという問題がある。
TAPは、.NET 4でTaskクラスが導入されて以来、使われるようになったパターンで、今後はこのパターンに統一されていくことになる(.NET 4.5では、主要な非同期APIが全てTAPに置き換わっている)。
特に、C# 5.0で導入されるawait演算子は、Taskクラスとの親和性がよく、TAPが有効である。Taskクラス自体は.NET 4/Silverlight 5で使えるので、それ以降であれば、TAPを前提に非同期APIを実装/利用するといいだろう。
■UIスレッドへの切り替え
前述のとおり、非同期処理の結果をUIに反映させるためには、別スレッドからUIスレッドに処理の流れを戻す必要がある。SilverlightやWPF、Metro(WinRT)でこの役割を担うのは、「ディスパッチャ(dispatcher: 配送係)」と呼ばれるオブジェクトだ。
Silverlightでは、UI要素など、UIスレッドにひも付いたオブジェクトがDependencyObjectクラス(System.Windows名前空間)から派生している。DependencyObjectクラスはDispatcherというプロパティを持っていて、これを使ってUIスレッドへの切り替えを行う。
List 3にディスパッチャの利用例を示す。DebugLogメソッドの内部でスレッドのIDをデバッグ出力するものとして、このコードをUIスレッド内で実行すると、List 4のような出力を得る。これは、Figure 6に示すような挙動をしている。
|
||
List 3: Dispatcherの利用例(上:C#、下:VB) |
|
|
List 4: List 3の実行結果の例 | |
スレッドIDは実行するたびに変化する可能性がある。 |
Figure 6: List 3の内部挙動 |
●同期コンテキスト
このUIスレッドへの切り替えは、フレームワークによって流儀が異なっている。同じXAML系フレームワークのWPFやMetroスタイル・アプリ(WinRT)でも、DependencyObjectがDispatcherプロパティを持っているところまでは共通しているが、メソッドのシグネチャが少しずつ異なる。まして、Windowsフォームのような別系統のフレームワークの場合、全く違う書き方が必要になる。
この差を吸収するためにあるのが同期コンテキスト(synchronization context)だ。SynchronizationContext(System.Threading名前空間)という抽象クラスの、Postメソッドを使って統一的な書き方ができる。
例えば、List 3を、同期コンテキストを使って書き直すと、List 5に示すようになる。この書き方ならば、WPFやMetroスタイル・アプリでも動作可能である。
|
||
List 5: 同期コンテキストの利用例(上:C#、下:VB) |
●同期コンテキストの隠ぺい
ここまでの説明を見て、「アプリ開発者がここまで面倒なことをしなければならないのか」と疑問に思った方もいるかもしれない。この手の面倒ごとは、フレームワークの中に隠してしまって、アプリ開発のレベルでは気にしなくてもいい状態にすべきだろう。
実際、WebClientクラスのDownlowdStringAsyncメソッドなど、.NET 2.0世代のEAP型の非同期APIは、内部で自動的に同期コンテキストを使っていて、アプリ開発者が意識する必要はない。
ところが、これはこれでいくつかの問題がある。
- WebClientクラスがそうなっているだけで、全てのEAP型APIが同期コンテキストを使っている保証はない
- あくまで「同期コンテキストを使え」というガイドラインがあるだけで、守られる保証はない
- 逆に、「同期コンテキストを使わない」という選択肢を取れない
- スレッド・プール上で続きの処理をする方法がない(毎回、UIスレッドを経由することになり、性能上の問題になることがある)
- 単体テストの際に困る(GUIアプリ中での動作と、テスト・プロジェクト中での動作が変わる)
同期コンテキストを一切意識させない作りになっているフレームワークでは、この手の問題にはまってしまうことも少なくない。
この結果、.NET 4世代のTaskクラスでは、タスクの実行場所を明示的に指定するためのタスク・スケジューラという仕組みが導入された。
●タスク・スケジューラ
Taskクラスを使う場合、どこでタスクを実行するかを明示的に指定できる。タスクの実行場所の管理を行うのがタスク・スケジューラで、TaskScheduler(System.Threading.Tasks名前空間)というクラスを使う。既定のタスク・スケジューラは、スレッド・プール上でタスクを実行する。
以下のように、FromCurrentSynchronizationContext静的メソッドを使うことで、同期コンテキスト上で(=Silverlightなどの場合はUIスレッド上で)タスク実行するためのタスク・スケジューラを取得できる。
|
||
スレッド・プール上でタスクを実行するためにタスク・スケジューラを明示的に指定するコード(上:C#、下:VB) |
続いて次のページでは、Silverlightでの具体的な非同期処理の書き方について説明していく。
INDEX | ||
[連載] 次世代技術につながるSilverlight入門 | ||
避けて通れない「非同期処理」を克服しよう | ||
1.非同期APIの一例/非同期の仕組み | ||
2.非同期APIのパターン /UIスレッドへの切り替え | ||
3.非同期処理の具体例/補足: C# 5.0(とVisual Basic 11) | ||
「連載:次世代技術につながるSilverlight入門」 |
- 第2回 簡潔なコーディングのために (2017/7/26)
ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている - 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう - 第1回 明瞭なコーディングのために (2017/7/19)
C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える - Presentation Translator (2017/7/18)
Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
|
|
- - PR -