じょいのーと

プログラミング関連のアレコレを書いています

非同期Bindingの動きについて

WPFのBindingを行う際に非同期指定を行うと値を非同期でBindingするように出来ますが、その挙動についてメモ。

Binding.IsAsyncプロパティ

XAMLに以下のようにIsAsync=True指定をすると、Bindingが非同期化されます。

<TextBlock Text="{Binding Hoge, IsAsync=True}" />

こいつが何者かについてはMSDNBinding.IsAsync プロパティ (System.Windows.Data)があるのでそこを見ればいいとして、具体的にどういう挙動をするのかについて簡単に書いてみます。

非同期BindingのBinding結果解決手順

  1. ThreadPoolからThreadを取得し、取得したThread上でBindingで指定するPathに存在するプロパティのgetterから値を取得する
  2. Bindingオブジェクトが取得した値をDispatcher.InvokeでUIスレッドへと転送する
  3. ValueConverter等で値の変換が行われる
  4. UI要素上に表示

以上の様な手順で、IsAsync指定したBindingは解決されます。

同期Bindingとの違い

同期的なBindingとの大きな違いとしては、Binding先プロパティへのアクセスを行うスレッドがUIスレッドではなくなる点です。
通常、XAML上で記述されたBindingはsetもgetもUIスレッド上で全て行われますが、IsAsync=True指定を行った瞬間にgetに関してはスレッドプール上のバックグラウンドスレッドで行われるようになります。

気をつけるべきこと

気をつけるべきこととして、実行スレッド依存の問題とパフォーマンスの問題があります。

1.実行スレッド依存の問題

WPFのUI要素はUIスレッド上からしかアクセス出来ないようになっているので困る系の問題です。
MVVMで作っていても、ViewModel層にUI要素(特にBitmapImageとか)を持たざるを得ない設計もあると思いますが、そういった場合には実行スレッドが切り替わると困ります。
例えば「初回はBitmap作ってFreezeして、次回以降はキャッシュを返す」みたいな動きを実装している時にIsAsyncを使ってしまうとBitmap生成のスレッドがUIスレッドでなくなるので例外が発生してBitmapImage作れない…などですね。
対処法としてはバックグラウンド スレッドで UI 要素を作るとメモリリークする (WPF) | grabacr.nétバックグラウンドスレッドでUI要素を作るともっと問題は深刻かもしれない。(WPF) | LOGarithm付近で紹介されている地雷を避けつつ、STA指定したThread上でUI要素を作るとgetterの実行スレッドは気にしないでも大丈夫になります。

2.パフォーマンスの問題

こっちの問題は単純で、一つのgetterに対して1つのThreadを使って値の取得&転送を行うことになるため、単純に処理のオーバーヘッド分だけ遅いです。
上で紹介したMSDNのページ内でも言及されていますが、そもそもgetterで時間のかかる処理しないで、どこか別のタイミングで値を生成してキャッシュしておけ…というのを基本方針にした方が無難です。
手元で数千個のBindingを全てIsAsync=Trueにしてみた感覚だと、値の取得タイミングとは別に総計で10%くらいの性能が劣化するようです。