26
votes

I have two ViewModel classes : PersonViewModel and PersonSearchListViewModel. One of the fields PersonViewModel implements is a profile image that is downloaded via WCF(cached locally in isolated storage). The PersonSearchListViewModel is a container class that holds a list of Persons. Since loading images is relatively heavy, PersonSearchListViewModel loads only images for the current, next and previous page (results are paged on UI)...to further improve the load of images I put the load of images on another thread. However multi-threading approach causes cross-thread access issues.

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();
        }
    }
}

How do you process data in ViewModel class in multi-threaded manner ? I know you have to use dispatcher on UI to update. How do you update ViewModel that is bound to UI ?

** EDIT **

There is also one more weird error happening. In the code below :

        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);
            }
        }

I get a cross-thread error when trying to create a new BitmapImage object in background thread. Why can't I create a new BitmapImage object on a background thread ?

3

3 Answers

62
votes

In order to update a DependencyProperty in a ViewModel, use the same dispatcher you would use to access any other UIElement:

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

Also, BitmapImages have to be instantiated on the UI thread. This is because it uses DependencyProperties, which can only be used on the UI thread. I have tried instantiating BitmapImages on separate threads and it just doesn't work. You could try to use some other means to store images in memory. For example, when you download the image, store it in a MemoryStream. Then a BitmapImage on the UI thread can set its source to the MemoryStream.

You may try instantiating the BitmapImages on the UI thread and then do everything else with the BitmapImage on another thread... but that would get hairy and I'm not even sure it would work. The following would be an example:

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);
2
votes

I believe you are having a cross threading issue with the UI thread.

Editing the bound object may force an update of the UI on the worker thread, which cannot succeed. You will likely need to do the InvokeRequired/Invoke hokey-pokey whenever you update the bound class.

You said you knew this already, but for reference:

MSDN on thread-safe calls to UI

0
votes

It can be achieved with 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);  
                });  
            }  
        });  
    }

But You have to construct WriteableBitmap in UI Thread, then loading can be performed in other thread.

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

See more explanaition on this blog post