CodeTheCat
CodeTheCat

Reputation: 43

await in WPF doesn't return to UI Thread

I have a strange behavior in WPF 4.5 (.net 4.5). I'm using the keywords await and async in order to run long-operations (for example load a big BitmapImage, base for a Image control). The problem is that the awaiter doesn't return to the main UI Thread, because I get teh famous Exception:

The calling thread cannot access this object because a different thread owns it.

Could someone help me?

Here my code:

Event handler of a button:

    private void GetExifData_Click(object sender, RoutedEventArgs e)
    {
        // Async method
        (new AnalyzeSingleImage()).RunExif(this);           
    }

The main method (in a separate class, same assembly)

    public async void RunExif(MainWindow win)
    {
      // here I run correctly code on the main UI Thread
      ..
      ..
      // ASYNC !!!
      BitmapImage bi = await LoadImageAsync(fileName);

      Image img = new Image();
      img.Source = bi;  // *********** HERE I GET THE EXCEPTION *************
      ..
      ..
    }

The Async method:

    private Task<BitmapImage> LoadImageAsync(string fileName)
    {
        return Task<BitmapImage>.Run(() => LoadImage(fileName));
    }

The long time method:

    private BitmapImage LoadImage(string fileName)
    {
        BitmapImage bi = new BitmapImage();
        bi.BeginInit();
        bi.UriSource = new Uri(fileName);
        bi.CacheOption = BitmapCacheOption.OnLoad;
        bi.EndInit();

        return bi;
    }

Someone could help me please?

Upvotes: 2

Views: 437

Answers (2)

CodeTheCat
CodeTheCat

Reputation: 43

at the end I used this approach:

On the main UI

 BitmapImage bi = await LoadImageAsync(fileName);
            Image img = new Image();
            img.Source = bi; 
            borderImg.Child = img;

Long task..

 private Task<BitmapImage> LoadImageAsync(string fileName)
    {
        return Task<BitmapImage>.Run(() => LoadImage(fileName));
    }

The trick is use the Freeze member!!

 private BitmapImage LoadImage(string fileName)
    {

        BitmapImage bi = new BitmapImage();
        bi.BeginInit();
        //bi.StreamSource = imgAsStream;
        bi.UriSource = new Uri(fileName);
        bi.CacheOption = BitmapCacheOption.OnLoad;
        bi.DecodePixelWidth = 400; // riduce l'utilizzo di memoria!
        bi.EndInit();
        bi.Freeze(); // IMPORTANTE, ALTRIMENTI RITORNANDO ALLA UI Thread ho un errore!!!!
        return bi;

    }

Upvotes: 1

usr
usr

Reputation: 171178

LoadImage runs on the thread-pool because you pushed it there using Task.Run. It is valid to use UI elements on different threads but you must access them on the same thread consistently.

The BitmapImage you created is now bound to a thread-pool thread. It cannot be combined with the main UI.

For that reason, it is best practice to only have UI on a single thread. Create the bitmap on the main thread.

If I remember WPF correctly creating a BitmapImage is very quick and loading the image is async anyway.

Upvotes: 2

Related Questions