1

I'm trying to load a image asynchronously.

MainWindow code

public partial class MainWindow : Window
{
    private Data data = new Data();
    public MainWindow()
    {
        InitializeComponent();
        this.DataContext = data;
    }
    private async void button_Click(object sender, RoutedEventArgs e)
    {
        data.Image = await Data.GetNewImageAsync();
    }
}

Data class

public class Data : INotifyPropertyChanged
{
    private BitmapImage _Image = new BitmapImage();
    public BitmapImage Image { get { return _Image; } set { _Image = value; OnPropertyChanged("Image"); } }

    public static BitmapImage GetNewImage()
    {
        return new BitmapImage(new Uri("http://www.diseno-art.com/news_content/wp-content/uploads/2012/09/2013-Jaguar-F-Type-1.jpg"));
    }

    public async static Task<BitmapImage> GetNewImageAsync()
    {
        return await Task.Run(() => GetNewImage());
    }

    public event PropertyChangedEventHandler PropertyChanged;
    public void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}

WPF code

<Button Name="button" Click="button_Click">Image</Button>
<Image Grid.Row="1" Source="{Binding Path=Image, UpdateSourceTrigger=PropertyChanged}"></Image>

Problem

I get the exception:

System.ArgumentException: "Must create DependencySource on same Thread as the DependencyObject."

... in this row: PropertyChanged(this, new PropertyChangedEventArgs(propertyName));

But if i change BitmapImage to string this code works fine.

What am I doing wrong?

1
  • As a note, setting UpdateSourceTrigger=PropertyChanged on the Image Binding is pointless. It only has an effect on TwoWay and OneWayToSource Bindings. Commented Oct 12, 2017 at 12:37

1 Answer 1

7

When a BitmapImage is created in a background thread, you have to make sure that it gets frozen before it is used in the UI thread.

You would have to load it yourself like this:

public static async Task<BitmapImage> GetNewImageAsync(Uri uri)
{
    BitmapImage bitmap = null;
    var httpClient = new HttpClient();

    using (var response = await httpClient.GetAsync(uri))
    {
        if (response.IsSuccessStatusCode)
        {
            using (var stream = new MemoryStream())
            {
                await response.Content.CopyToAsync(stream);
                stream.Seek(0, SeekOrigin.Begin);

                bitmap = new BitmapImage();
                bitmap.BeginInit();
                bitmap.CacheOption = BitmapCacheOption.OnLoad;
                bitmap.StreamSource = stream;
                bitmap.EndInit();
                bitmap.Freeze();
            }
        }
    }

    return bitmap;
}

Or shorter with BitmapFrame.Create, which returns an already frozen BitmapSource:

public static async Task<BitmapSource> GetNewImageAsync(Uri uri)
{
    BitmapSource bitmap = null;
    var httpClient = new HttpClient();

    using (var response = await httpClient.GetAsync(uri))
    {
        if (response.IsSuccessStatusCode)
        {
            using (var stream = new MemoryStream())
            {
                await response.Content.CopyToAsync(stream);
                stream.Seek(0, SeekOrigin.Begin);

                bitmap = BitmapFrame.Create(
                    stream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
            }
        }
    }

    return bitmap;
}

Note that the second method requires to change the type of your Image property to BitmapSource (or even better, ImageSource), which would provide greater flexibility anyway.


An alternative method without any manual download might look like shown below. It also does not require to freeze the BitmapImage, because it is not created in a Task thread.

public static Task<BitmapSource> GetNewImageAsync(Uri uri)
{
    var tcs = new TaskCompletionSource<BitmapSource>();
    var bitmap = new BitmapImage(uri);

    if (bitmap.IsDownloading)
    {
        bitmap.DownloadCompleted += (s, e) => tcs.SetResult(bitmap);
        bitmap.DownloadFailed += (s, e) => tcs.SetException(e.ErrorException);
    }
    else
    {
        tcs.SetResult(bitmap);
    }

    return tcs.Task;
}
Sign up to request clarification or add additional context in comments.

4 Comments

Can't one set bitmap.UriSource instead of bitmap.StreamSource to avoid manual downloading code (leaving all cache options and freezing the same)?
Not when you want to freeze the BitmapImage immediately. You could register a DownloadCompleted event handler where you would freeze it, but you couldn't pass it to the UI before the handler was called.
@Evk please see the edit for an alternative w/o manual download.
Yes thanks, that thought already came to my mind when you mentioned DownloadCompleted handler.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.