-1

I am trying to change an image Source in WPF and it throws a "System.InvalidOperationException" exception ('The calling thread cannot access this object because a different thread owns it.'), now, I tried to put the code in a Application.Current.Dispatcher.Invoke(Action) block but it still throws the error, it is probably relevant that I am trying to change the image from an async method as follows:

// Calling ChangeImage here
public async void LocalKill()
{
    for (int i = 0; i < 12; i++)
    {
        sprite.ChangeImage("@$\"Resources\\Images\\Player\\PlayerDeath{i % 2 + 1}.png\"");
        await Task.Delay(1000 / 10);
    }
}

// In "Sprite" class
public void ChangeImage(string imagePath)
{
    var bitmap = BitmapFromPath(imagePath);

    // UI Objects need to be changed in an STA thread
    Application.Current.Dispatcher.Invoke(() => image.Source = bitmap);
}

I tried changing the image source, expected it to change the image visually but instead it threw the error

  • You can't modify the UI from a background thread. What does this code do though? `async void LocalKill` is a bug because `async void` method can't be awaited. If you had an a `async` method in the main form, after each `await` you'd be back in the UI thread and `image.Source` wouldn't throw. Changing images every 100ms is too expensive and jittery though. If you want smooth animations and transitions you should use WPF's built-in animation support. – Panagiotis Kanavos Apr 05 '23 at 14:58
  • Check WPF's [Animation Overview](https://learn.microsoft.com/en-us/dotnet/desktop/wpf/graphics-multimedia/animation-overview?view=netframeworkdesktop-4.8) and [this SO question](https://stackoverflow.com/questions/29282137/wpf-animate-image-source-change) that uses keyframe animations to switch between different pictures. – Panagiotis Kanavos Apr 05 '23 at 15:22

1 Answers1

0

Use the dispatcher that is associated with the Image element and call Freeze() on bitmap.

public void ChangeImage(string imagePath)
{
    var bitmap = BitmapFromPath(imagePath);
    bitmap.Freeze();

    image.Dispatcher.Invoke(() => image.Source = bitmap);
}
mm8
  • 163,881
  • 10
  • 57
  • 88
  • tried that, exact same thing. I also tried adding an imageLock object to make sure I only interact with the image property at once nevermind where I use it – YonatanZiv Apr 05 '23 at 14:55
  • So how and where do you create the `Image` element? – mm8 Apr 05 '23 at 14:55
  • The Image element is declared in the XAML file of Sprite.xaml and initialized in the InitializeComponent method (default), it's declared like that: . Probably should mention that Sprite is a UserControl – YonatanZiv Apr 05 '23 at 14:58
  • Then the above code should work. Please provide a reproducible sample of your issue if you need any further help. How is `BitmapFromPath` implemented? – mm8 Apr 05 '23 at 15:00
  • The code is probably using a GDI+ bitmap which is also subject to cross-thread restrictions. 100ms is too short an interval to keep redrawing. The default system timer resolution is 16.5ms which means you won't get 10fps anyway. If you want a smooth animation use WPF's built-in animation support. It's a completely different mechanism from load/replace image/redraw window with a far more predictable timer. – Panagiotis Kanavos Apr 05 '23 at 15:01
  • Did you try to call `Freeze()` on the `BitmapImage`? – mm8 Apr 05 '23 at 15:05
  • Oh I actually wasn't aware that WPF had an animation support, I'll definitely look into it. And I'll try to reproduce this error in a smaller project, the implementation of BitmapFromPath isn't the problem here as when I debug it, it returns a valid bitmap with the wanted image. – YonatanZiv Apr 05 '23 at 15:05
  • yes @mm8 I did call Freeze – YonatanZiv Apr 05 '23 at 15:06
  • Not in the code you have posted...Where is the exception thrown exactly if you run the latest code in my answer? – mm8 Apr 05 '23 at 15:07
  • @mm8 , so I tried a bunch of other things in the meantime but none worked sadly. I tried using the Freeze() method again but it throws the exact same exception at the Invoke line (again) saying that I am trying to access the object from another thread that it was created on. – YonatanZiv Apr 05 '23 at 16:16