中の技術日誌ブログ

C#とC++/CLIと
VBと.NETとWindowsで戯れる
 

目次

Blog 利用状況

ニュース

自己紹介

東京でソフトウェアエンジニアをやっています。
お仕事大募集中です。
記事執筆や、講師依頼とかでも何でもどうぞ(*^_^*)
似顔絵 MSMVPロゴ
MSMVP Visual C# Since 2004/04-2013/03

記事カテゴリ

書庫

日記カテゴリ

00-整理

01-MSMVP

2012年11月22日 #

WPFで画像の読み込みを非同期で行いたい。async/awaitでのやりかた。

一覧データを作って、そこに画像をはめ込みたい。そんな要望ありますよね。

でも画像はネットの向こう側。できれば非同期で取ってきて取って来られたら画像をはめ込んでいく。みたいな仕組みにしたいですよね。

private BitmapImage _画像;
public BitmapImage 画像{
    get  {
        if (_画像 == null) { 画像取得(); return null; }
        else { return _画像;}
    }
    set { _画像 = value; OnPropertyChanged("画像");}
}

WPFエンジンが画像をバインドに来たときに、初回はnullを返しておいて、非同期に画像取得しに行って、画像が取得されると画像プロパティにsetされるので、再度バインドが取りに来てくれる。こういう目論見です。

画像を取得する実際の処理って言うのは

private Task<BitmapImage> 画像取得Async()
{
    return Task.Run(() =>
                        {
                             BitmapImage bi = new BitmapImage();
                            bi.BeginInit();
                            bi.UriSource = new Uri(URL);
                            bi.EndInit();

                            return bi;
                        });
}

こういう処理です。

まずはじめに

private async void  画像取得(){画像 = await 画像取得Async(); }

こういう処理を書いてみました。

画像取得が成功して戻ってきたBitmapImageを直接画像に貼り付けるってことですね。

例外発生!

“DependencySource は、DependencyObject と同じ Thread 上で作成する必要があります。"

"Must create DependencySource on same Thread as the DependencyObject."

ここで本当の原因にたどり着けないのが、僕の残念なところですが・・・

エラーが発生したのはOnPropertyChangedの先

場所 System.Windows.DependencyObject.ValidateSources(DependencyObject d, DependencySource[] newSources, Expression expr)
場所 System.Windows.Expression.ChangeSources(DependencyObject d, DependencyProperty dp, DependencySource[] newSources)
場所 System.Windows.Data.BindingExpressionBase.ChangeSources(DependencyObject target, DependencyProperty dp, WeakDependencySource[] newSources)
場所 System.Windows.Data.BindingExpressionBase.ChangeSources(WeakDependencySource[] newSources)
場所 System.Windows.Data.BindingExpression.ChangeWorkerSources(WeakDependencySource[] newWorkerSources, Int32 n)
場所 MS.Internal.Data.ClrBindingWorker.ReplaceDependencySources()
場所 MS.Internal.Data.ClrBindingWorker.NewValueAvailable(Boolean dependencySourcesChanged, Boolean initialValue, Boolean isASubPropertyChange)
場所 MS.Internal.Data.PropertyPathWorker.UpdateSourceValueState(Int32 k, ICollectionView collectionView, Object newValue, Boolean isASubPropertyChange)
場所 MS.Internal.Data.ClrBindingWorker.OnSourcePropertyChanged(Object o, String propName)
場所 MS.Internal.Data.PropertyPathWorker.OnPropertyChanged(Object sender, PropertyChangedEventArgs e)
場所 System.Windows.WeakEventManager.ListenerList`1.DeliverEvent(Object sender, EventArgs e, Type managerType)
場所 System.ComponentModel.PropertyChangedEventManager.OnPropertyChanged(Object sender, PropertyChangedEventArgs args)

です。

 

そもそもawaitすれば一度呼び出し元にもどり、非同期処理が完了したらメインスレッド(正確には呼び出し元スレッド)にinvokeされ、後続処理が呼ばれるという認識です。

ですので、

var sync = SynchronizationContext.Current;
var asy = 画像取得Async();
asy.Wait();
sync.Post(o =>
              {
                  画像 = asy.Result;
              }, null
    );

こんなことすれば、処理が呼び出し元に戻る事も無く、確実にメインスレッドで呼び出されるので大丈夫だろうと思って試してみたところ。

同じ例外発生!!

BitmapImageを非同期の先で作成しているのが悪いんじゃないのか?

private Task<MemoryStream> 画像取得Async2()
{
    return Task.Run(() =>
    {
        var bytes = new WankumaWebClient().DownloadData(URL);
        return new MemoryStream(bytes);
    });
}

このようなAsyncメソッドにして

var x = await 画像取得Async2();

BitmapImage bi = new BitmapImage();
bi.BeginInit();
bi.StreamSource = x;
bi.EndInit();
画像 = bi;

このような呼び出しに変えてみたところばっちり動くようになりました。

BitmapImageはWPFのメインスレッドで作成しないといけないのね・・・・今思えば当たり前のことです。

ちなみに

画像 = await 画像取得Async3(new BitmapImage());

こんな感じで、メインスレッドからBitmapImageを供給してやって

bi.BeginInit();
bi.UriSource = new Uri(URL);
bi.EndInit();

こういうロジックを非同期側でやろうとしてもだめでした。

BitmapImageの操作はUIスレッドで!

posted @ 12:17 | Feedback (72)