連載:次世代技術につながるSilverlight入門 避けて通れない「非同期処理」を克服しよう 岩永 信之2012/08/02 |
|
Silverlightアプリを開発するうえで、避けたくても避けて通れないのが「非同期処理」だ。ネットワークへのアクセスなど、時間がかかる処理は非同期版のAPIしか提供されていない*1。
*1 さらにいうと、(Windows 8向けの)Metroスタイル・アプリ開発ではこの傾向がより強くなる。ネットワークどころか、ローカルのストレージへのアクセスですら、非同期APIのみとなっている。 |
「非同期(asynchrony)」という言葉は、「ある処理の実行中に、別の処理を止めない」という意味である。SilverlightアプリなどのGUIアプリで特に重要なのは、重たい処理をするときに、UIスレッド(=エンド・ユーザーからの入力を受け付けたり、UIを描画したりするスレッド)を止めないことだ。つまり、重たい処理は非同期に行わねばならない。
そこで今回は、.NETにおける非同期処理の書き方を、いくつかのフレームワークで対比しながら説明していこう。
■非同期APIの一例
例えば、ネット越しにテキストをダウンロードして、画面上(仮に、「Description」という名前を付けたテキストボックス)に表示することを考えてみよう。
WPF(デスクトップ版の.NET Frameworkの場合)であれば、List 1のような書き方ができる。
|
||
List 1: ネットワークに同期的にアクセスする例(上:C#、下:VB) |
ただし、このような書き方をすると、ダウンロードが完了するまでの間、アプリがフリーズしてしまう。アプリのフリーズはエンド・ユーザーに与える印象が非常に悪いため、当然、非推奨である。
「非推奨なものは最初から提供しない」というのが、Silverlightの方針だ。そこで、WebClientクラス(System.Net名前空間)から同期版のAPIが削除された。List 1と同様のことをするのに、List 2のような書き方が必要となる。
|
||
List 2: ネットワークに非同期的にアクセスする例(上:C#、下:VB) |
ちなみに、この書き方(=Completedイベントを使うもの)は、「EAP」(後述)と呼ばれるパターンで、このほかにもいくつかの非同期APIパターンが存在する(EAPは、.NET Framework 2.0〜3.5のころに主流だった書き方で、現在はTaskクラスを使ったパターンに置き換えられ始めている)。
■非同期の仕組み
「同期的な処理をするとアプリがフリーズする」といわれても、すぐにはイメージが浮かばないかもしれない。少し補足しておこう。
Figure 1は、GUIアプリが内部的にどういう動作をしているかを大まかに表したものである。
Figure 1: GUIアプリの動作の概念図 |
まずは、この中に出てくるUIスレッド、スレッド・プール、I/O待ちなどについて説明していこう。
●UIスレッド
SilverlightやWPF、あるいは、.NETに限らずグラフィックを扱うほとんどのフレームワークでは、エンド・ユーザーからの入力受け付けと、UI要素の更新処理を一手に担う専用のスレッドを持っている。このスレッドを「UIスレッド」(あるいは、「メイン・スレッド」などとも)呼ぶ(Figure 2)。パフォーマンス上の理由から、UI要素はスレッド安全(thread-safe)には作られておらず、必ずこのUIスレッドという、単一のスレッド上で操作する必要がある。
通常、アプリは、エンド・ユーザーからの入力を受け取って、何らかの処理をした結果をUIに反映させる。この「何らかの処理」において、重たい処理やI/O待ちなどでUIスレッドを止めてしまうと、アプリがフリーズしてしまう。
Figure 2: UIスレッド |
そこで、重たい処理はUIスレッドとは別のスレッドで実行する必要がある。その一方で、別スレッドでの計算結果をUIに反映させるためには、Figure 3に示すように、UIスレッドに処理を戻す必要がある(フレームワークによって流儀は異なるが、そのための仕組みが提供されている)。
Figure 3: UIスレッドに処理を戻す |
●スレッド・プール
スレッドの作成は非常に負担が大きく、OS全体のパフォーマンスを落としかねない。そこで、少数のスレッドを立てっぱなしにして、使い回すことで効率よくタスクをこなしていく仕組みがあり、これを「スレッド・プール(thread pool: スレッドの貯め置き、共用)」と呼ぶ。Figure 4に示すように、実行したいタスクを一度、キューにためておき、スレッドに空きができ次第、実行していく。
Silverlight 5/.NET Framework 4以降では、Taskクラス(System.Threading.Tasks名前空間)を介してスレッド・プールを利用できる*2。また、タイマー(=Timerクラス(System.Threading名前空間)を利用)や、後述するI/O待ちの結果を受け取るコールバック呼び出しでも、スレッド・プールが利用される。
Figure 4: スレッド・プール |
*2 一方で、Threadクラス(System.Threading名前空間)を使ってスレッドを直接立てて使う場面はほとんどなくなった。 |
●I/O待ち
CPUの外側にある、ストレージやネットワークなどのハードウェアに対する操作(=データの送受信)を、「I/O(input/output)」と呼ぶ。
CPUの動作速度と比べると、I/Oにかかる時間は非常に長い。その間は本当にただ待っているだけで、CPUやメモリを必要としないため、スレッドは解放すべきである。
実際、Figure 5に示すように、OSはスレッドを解放してI/O待ちするための機能を提供している。I/O処理を行いたい側は、I/O完了後に呼び出してほしいコールバックをOSに登録し、すぐにスレッド上での処理を終了する。I/O完了後のコールバック実行はスレッド・プール上で行われる。
Figure 5: I/O待ち |
I/Oに関連するAPI(例えば、WebClientクラスのDownlowdStringAsyncメソッドなど)は、このような仕組みを使って非同期I/O待ちを行っている。
次のページでは、非同期APIのパターン(APM/EAP/TAP)について説明する。
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 -