Welcome toVigges Developer Community-Open, Learning,Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
1.3k views
in Technique[技术] by (71.8m points)

wpf - async load BitmapImage in C#

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?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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 BitmatImage, 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;
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to Vigges Developer Community for programmer and developer-Open, Learning and Share

2.1m questions

2.1m answers

63 comments

56.6k users

...