0

I'm writing a class that can asynchronously load BitmapImage from a local file or a web link. I wait for BitmapImage to end downloading by using the DownloadCompleted and DownloadFailed event, then freeze it and pass it from worker thread to UI thread. But my code doesn't work, if I set web link as BitmapImage's UriSource, those event handlers will never be called. However, I notice that the WPF Image control can be notified when its source(BitmapImage) downloads completely and then show it, so I guess there must be some ways I dont't know to get notfied when a BitmapImage finish loading. Does anyone knows? Here is my code:

BitmapImage image = await Task.Run(async () =>
{
    BitmapImage image = new BitmapImage();
    image.BeginInit();
    image.CacheOption = BitmapCacheOption.OnLoad;
    image.UriSource = uri;
    image.EndInit();


    if (image.IsDownloading)
    {
        using EventWaitHandle handle = new EventWaitHandle(
            false, EventResetMode.ManualReset);

        void RemoveEventHandler()
        {
            image.DownloadCompleted -= OnDownloadCompleted;
            image.DownloadFailed -= OnDownloadFailed;
        }

        void OnDownloadCompleted(object? sender, EventArgs args)
        {
            handle.Set();
            RemoveEventHandler();
        }

        void OnDownloadFailed(object? sender, EventArgs args)
        {
            handle.Set();
            RemoveEventHandler();
        }

        image.DownloadCompleted += OnDownloadCompleted;
        image.DownloadFailed += OnDownloadFailed;
        await Task.Run(handle.WaitOne);
    }

    image.Freeze();
    return image;
});
AlpacaMan
  • 465
  • 2
  • 7
  • 24
  • Have you considered manually downloading images by e.g. a HttpRequest and create a BitmapSource from the response buffer? You would have finer control over the progress. – Clemens Jun 21 '23 at 14:56

2 Answers2

0

You should hook up the event handler before you set the UriSource and the download starts. This works for me:

BitmapImage image = new BitmapImage();
image.DownloadCompleted += OnDownloadCompleted;
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnLoad;
image.UriSource = new System.Uri("https://someurl/pic.jpg");
image.EndInit();

void OnDownloadCompleted(object? sender, EventArgs args)
{
    BitmapImage image = (BitmapImage)sender;
    image.DownloadCompleted -= OnDownloadCompleted;
    //...
}
mm8
  • 163,881
  • 10
  • 57
  • 88
0

It appears that the DownloadCompleted and DownloadFailed events are not fired in a background thread, so you can not easily wrap your code in a Task.Run call.

Besides that, instead of waiting for an EventWaitHandle you could use a TaskCompletionSource in a method like this:

private async Task<BitmapImage> LoadBitmapImageAsync(Uri uri)
{
    var image = new BitmapImage();
    image.BeginInit();
    image.CacheOption = BitmapCacheOption.OnLoad;
    image.UriSource = uri;
    image.EndInit();

    if (image.IsDownloading)
    {
        var tcs = new TaskCompletionSource();

        void OnDownloadCompleted(object sender, EventArgs args)
        {
            tcs.TrySetResult();
            RemoveEventHandlers();
        }

        void OnDownloadFailed(object sender, ExceptionEventArgs args)
        {
            tcs.TrySetException(args.ErrorException);
            RemoveEventHandlers();
        }

        void RemoveEventHandlers()
        {
            image.DownloadCompleted -= OnDownloadCompleted;
            image.DownloadFailed -= OnDownloadFailed;
        }

        image.DownloadCompleted += OnDownloadCompleted;
        image.DownloadFailed += OnDownloadFailed;

        await tcs.Task;
    }

    image.Freeze();
    return image;
}

You may however manually download images like this:

private Task<BitmapImage> LoadBitmapImageAsync(Uri uri)
{
    return Task.Run(async () =>
    {
        var httpClient = new HttpClient();
        var responseStream = await httpClient.GetStreamAsync(uri);
        var image = new BitmapImage();

        using (var memoryStream = new MemoryStream())
        {
            await responseStream.CopyToAsync(memoryStream);

            image.BeginInit();
            image.CacheOption = BitmapCacheOption.OnLoad;
            image.StreamSource = memoryStream;
            image.EndInit();
            image.Freeze();
        }

        return image;
    });
}
Clemens
  • 123,504
  • 12
  • 155
  • 268