WPF の Dispatcher について勉強してみた

WPFDispatcher について勉強がてら自分なりに得た理解をまとめてみました。

WPF アプリケーションは、レンダリング(描画)用とユーザーインターフェイス用の2つのスレッドを用いて動作します。
レンダリングスレッドはバックグラウンド動作のため、開発者はアクセスできません。開発者が扱えるのは、ユーザーインターフェイス用スレッド(以下 UIスレッド)のみとなります。そしてこの UI スレッドは、パフォーマンス上の理由により(スレッドセーフにするとオーバーヘッドが発生し、描画遅延等の問題を起こすためか)スレッドセーフに設計されてないのが特徴です。
WPFでは、ほとんどのオブジェクトが UI スレッド上で動作する「シングルスレッドモデル」を採用しているため、UI スレッド外からそのオブジェクトにアクセスすると、例外が発生します。

例えば、以下のように ListBox へのアイテム追加を並列実行すると、InvalidOperationException がスローされます。


しかしアプリケーションの要件によっては非同期にオブジェクトにアクセスしたいシーンもある筈です。また重い処理を実行している間も UI を更新したいケースもあります。そこで WPF では、スレッド関連の操作を開発者に提供するため、Dispatcher クラスを用意しています。
Dispatcher.BeginInvoke を使えば非同期に Viewの要素にアクセスできます。

並列処理で追加されたアイテム。並列で追加されたため、数値が昇順で並んでない


また Dispatcher は UIスレッドのキューも管理しています。WPF のUIスレッドのキューは一般的なキューと異なり優先順位を指定でき、DispatchPriority の優先順位に基づき処理を実行します。以下の例は、イベントの完了を待たずに UI の更新を行うサンプルです。Thread.Sleep を使い 3秒間アプリケーションを停止しますが、ListBox は 3秒待たず直ちに更新されます。

ViewModel 内で Dispatcher を使う

Dispatcher を使う場面は View だけとは限りません。Application.Current.Dispatcher を使い ViewModel の通知プロパティやコマンドの CanExecute メソッド内で使うことも可能です。私のプロジェクトでは、XP で一部描画遅延が発生する旧い機種があるので、ViewModel 内で Application.Current.Dispatcher を使い対応してます。

参考記事連載:WPF入門:第7回 WPF UI要素の基礎とレイアウト用のパネルを学ぼう (1/2)
参考記事Dispatcher を使用して応答性の高いアプリケーションを構築する
参考記事スレッド モデル