Invalid cross-thread access issue

Ender picture Ender · Dec 17, 2009 · Viewed 44k times · Source

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 ?

Answer

Phil picture Phil · Dec 18, 2009

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