web-dev-qa-db-ja.com

無効なクロススレッドアクセスの問題

2つのViewModelクラスがあります。PersonViewModelとPersonSearchListViewModelです。 PersonViewModelが実装するフィールドの1つは、WCFを介してダウンロードされるプロファイルイメージです(分離ストレージにローカルにキャッシュされます)。 PersonSearchListViewModelは、個人のリストを保持するコンテナークラスです。画像の読み込みは比較的重いため、PersonSearchListViewModelは現在、次、前のページの画像のみを読み込みます(結果はUIでページングされます)...画像の読み込みをさらに改善するために、画像の読み込みを別のスレッドに配置します。ただし、マルチスレッドアプローチでは、クロススレッドアクセスの問題が発生します。

PersonViewModel:

public void RetrieveProfileImage()
{
    Image profileImage = MemorialDataModel.GetImagePerPerson(Person);
    if (profileImage != null)
    {
        MemorialDataModel.ImageManager imgManager = new MemorialDataModel.ImageManager();
        imgManager.GetBitmap(profileImage, LoadProfileBitmap);
    }
}

private void LoadProfileBitmap(BitmapImage bi)
{
    ProfileImage = bi;
    // update 
    IsProfileImageLoaded = true;
}

private BitmapImage profileImage;
public BitmapImage ProfileImage
{
    get
    {
        return profileImage;
    }
    set
    {
        profileImage = value;
        RaisePropertyChanged(new System.ComponentModel.PropertyChangedEventArgs("ProfileImage"));
    }
}

PersonSearchListViewModel:

private void LoadImages()
{
    // load new images 
    Thread loadImagesThread = new Thread(new ThreadStart(LoadImagesProcess));
    loadImagesThread.Start();

    //LoadImagesProcess(); If executed on the same thread everything works fine 
}

private void LoadImagesProcess()
{
    int skipRecords = (PageIndex * PageSize);
    int returnRecords;

    if (skipRecords != 0)
    {
        returnRecords = 3 * PageSize; // page before, cur page and next page 
    }
    else
    {
        returnRecords = 2 * PageSize;   // cur page and next page 
    }

    var persons = this.persons.Skip(skipRecords).Take(returnRecords);

    // load images 
    foreach (PersonViewModel pvm in persons)
    {
        if (!pvm.IsProfileImageLoaded)
        {
            pvm.RetrieveProfileImage();
        }
    }
}

マルチスレッドの方法でViewModelクラスのデータをどのように処理しますか?更新するには、UIでディスパッチャーを使用する必要があることを知っています。 UIにバインドされているViewModelをどのように更新しますか?

**編集**

また、奇妙なエラーが発生しています。以下のコードでは:

        public void GetBitmap(int imageID, Action<BitmapImage> callback)
        {
            // Get from server 
            bitmapCallback = callback;

            memorialFileServiceClient.GetImageCompleted += new EventHandler<GetImageCompletedEventArgs>(OnGetBitmapHandler);
            memorialFileServiceClient.GetImageAsync(imageID);
        }

        public void OnGetBitmapHandler(object sender, GetImageCompletedEventArgs imageArgs)
        {
            if (!imageArgs.Cancelled)
            {
                // I get cross-thread error right here 
                System.Windows.Media.Imaging.BitmapImage bi = new BitmapImage();
                ConvertToBitmapFromBuffer(bi, imageArgs.Result.Image);

                // call call back
                bitmapCallback.Invoke(bi);
            }
        }

バックグラウンドスレッドで新しいBitmapImageオブジェクトを作成しようとすると、クロススレッドエラーが発生します。バックグラウンドスレッドで新しいBitmapImageオブジェクトを作成できないのはなぜですか?

26
Ender

ViewModelのDependencyPropertyを更新するには、他のUIElementへのアクセスに使用するのと同じディスパッチャーを使用します。

System.Windows.Deployment.Current.Dispatcher.BeginInvoke(() => {...});

また、BitmapImagesはUIスレッドでインスタンス化する必要があります。これは、UIスレッドでのみ使用できるDependencyPropertiesを使用するためです。別のスレッドでBitmapImagesをインスタンス化しようとしましたが、機能しません。他の方法を使用して、画像をメモリに保存することもできます。たとえば、イメージをダウンロードするときに、MemoryStreamに保存します。次に、UIスレッドのBitmapImageは、そのソースをMemoryStreamに設定できます。

UIスレッドでBitmapImagesをインスタンス化してから、別のスレッドでBitmapImageを使用して他のすべてのことを実行することもできます...しかし、それは厄介であり、うまくいくかどうかさえわかりません。次に例を示します。

System.Windows.Media.Imaging.BitmapImage bi = null;
using(AutoResetEvent are = new AutoResetEvent(false))
{
    System.Windows.Deployment.Current.Dispatcher.BeginInvoke(() =>
    {
        bi = new BitmapImage();
        are.Set();
    });
    are.WaitOne();
}

ConvertToBitmapFromBuffer(bi, imageArgs.Result.Image);
bitmapCallback.Invoke(bi);
62
Phil

UIスレッドにクロススレッドの問題があると思います。

バインドされたオブジェクトを編集すると、ワーカースレッドのUIが強制的に更新される可能性があり、成功しません。バインドされたクラスを更新するたびに、InvokeRequired/Invoke hokey-pokeyを実行する必要がある可能性があります。

あなたはすでにこれを知っていると言いましたが、参考までに:

Iへのスレッドセーフな呼び出しのMSDN

2
Scott P

これはWriteableBitmapで実現できます。

    public void LoadThumbAsync(Stream src, 
                    WriteableBitmap bmp, object argument)  
    {  
        ThreadPool.QueueUserWorkItem(callback =>  
        {  
            bmp.LoadJpeg(src);  
            src.Dispose();  
            if (ImageLoaded != null)  
            {  
                Deployment.Current.Dispatcher.BeginInvoke(() =>  
                {  
                    ImageLoaded(bmp, argument);  
                });  
            }  
        });  
    }

ただし、UIスレッドでWriteableBitmapを作成する必要があります。そうすると、他のスレッドでロードを実行できます。

    void DeferImageLoading( Stream imgStream )  
    {  
        // we have to give size  
        var bmp = new WriteableBitmap(80, 80);  
        imageThread.LoadThumbAsync(imgStream, bmp, this);  
    }  

これについての詳しい説明を見る ブログ投稿

0
Ernest