在 WPF 的后台线程中加载图像 [英] Loading an image in a background thread in WPF

查看:59
本文介绍了在 WPF 的后台线程中加载图像的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

本网站和其他论坛上已经有很多关于此的问题,但我还没有找到真正有效的解决方案.

There are a bunch of questions about this already on this site and other forums, but I've yet to find a solution that actually works.

这是我想要做的:

  • 在我的 WPF 应用中,我想加载一个图像.
  • 图片来自网络上的任意 URI.
  • 图片可以是任何格式.
  • 如果我多次加载同一张图片,我想使用标准的 Windows 互联网缓存.
  • 图像加载和解码应该同步进行,但不是在 UI 线程上进行.
  • 最后,我应该得到一些可以应用于 <Image> 的源属性的东西.

我尝试过的事情:

  • 在 BackgroundWorker 上使用 WebClient.OpenRead().工作正常,但不使用缓存.WebClient.CachePolicy 仅影响该特定 WebClient 实例.
  • 在后台工作器上使用 WebRequest 而不是 WebClient,并设置 WebRequest.DefaultCachePolicy.这正确地使用了缓存,但我还没有看到一个例子,它不会给我一半的时间看起来损坏的图像.
  • 在 BackgroundWorker 中创建 BitmapImage,设置 BitmapImage.UriSource 并尝试处理 BitmapImage.DownloadCompleted.如果 BitmapImage.CacheOption 被设置,这似乎使用缓存,但似乎没有处理 DownloadCompleted 因为 BackgroundWorker 立即返回.

我已经断断续续地为此苦苦挣扎了几个月,我开始认为这是不可能的,但你可能比我更聪明.你怎么看?

I've been struggling with this off-and-on for literally months and I'm starting to think it's impossible, but you're probably smarter than me. What do you think?

推荐答案

我以多种方式解决了这个问题,包括使用 WebClient 和仅使用 BitmapImage.

I have approached this problem in several ways, including with WebClient and just with BitmapImage.

最初的建议是使用 BitmapImage(Uri, RequestCachePolicy) 构造函数,但我意识到我测试此方法的项目仅使用本地文件,而不是 Web.更改指南以使用我的其他经过测试的网络技术.

Original suggestion was to use the BitmapImage(Uri, RequestCachePolicy) constructor, but I realized my project where I tested this method was only using local files, not web. Changing guidance to use my other tested web technique.

您应该在后台线程上运行下载和解码,因为在加载过程中,无论是同步还是下载图像后,解码图像所需的时间很少但很重要.如果您正在加载许多图像,这可能会导致 UI 线程停顿.(这里还有一些其他复杂问题,例如 DelayCreation,但它们不适用于您的问题.)

You should run the download and decoding on a background thread because during loading, whether synchronous or after download the image, there is a small but significant time required to decode the image. If you are loading many images, this can cause the UI thread to stall. (There are a few other intricacies here like DelayCreation but they don't apply to your question.)

有几种方法可以加载图像,但我发现要在 BackgroundWorker 中从 Web 加载,您需要使用 WebClient 或类似的类自己下载数据.

There are a couple ways to load an image, but I've found for loading from the web in a BackgroundWorker, you'll need to download the data yourself using WebClient or a similar class.

请注意,BitmapImage 在内部使用 WebClient,此外它还有很多错误处理和凭据设置以及我们必须针对不同情况弄清楚的其他事项.我提供了这个片段,但它只在有限的情况下进行了测试.如果您要处理代理、凭据或其他情况,则必须稍微调整一下.

Note that BitmapImage internally uses a WebClient, plus it has a lot of error handling and settings of credentials and other things that we'd have to figure out for different situations. I'm providing this snippet but it has only been tested in a limited number of situations. If you are dealing with proxies, credentials, or other scenarios you'll have to massage this a bit.

BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += (s, e) =>
{
    Uri uri = e.Argument as Uri;

    using (WebClient webClient = new WebClient())
    {
        webClient.Proxy = null;  //avoids dynamic proxy discovery delay
        webClient.CachePolicy = new RequestCachePolicy(RequestCacheLevel.Default);
        try
        {
            byte[] imageBytes = null;

            imageBytes = webClient.DownloadData(uri);

            if (imageBytes == null)
            {
                e.Result = null;
                return;
            } 
            MemoryStream imageStream = new MemoryStream(imageBytes);
            BitmapImage image = new BitmapImage();

            image.BeginInit();
            image.StreamSource = imageStream;
            image.CacheOption = BitmapCacheOption.OnLoad;
            image.EndInit();

            image.Freeze();
            imageStream.Close();

            e.Result = image;
        }
        catch (WebException ex)
        {
            //do something to report the exception
            e.Result = ex;
        }
    }
};

worker.RunWorkerCompleted += (s, e) =>
    {
        BitmapImage bitmapImage = e.Result as BitmapImage;
        if (bitmapImage != null)
        {
            myImage.Source = bitmapImage;
        }
        worker.Dispose();
    };

worker.RunWorkerAsync(imageUri);

我在一个简单的项目中对此进行了测试,效果很好.我不是 100% 关于它是否命中缓存,但从我从 MSDN、其他论坛问题和反射到 PresentationCore 中可以看出,它应该命中缓存.WebClient包装了WebRequest,它包装了HTTPWebRequest等等,缓存设置每层向下传递.

I tested this in a simple project and it works fine. I'm not 100% about whether it is hitting the cache, but from what I could tell from MSDN, other forum questions, and Reflectoring into PresentationCore it should be hitting the cache. WebClient wraps WebRequest, which wraps HTTPWebRequest, and so on, and the cache settings are passed down each layer.

BitmapImage BeginInit/EndInit 对确保您可以同时设置所需的设置,然后在 EndInit 期间执行.如果您需要设置任何其他属性,您应该使用空构造函数并像上面一样写出 BeginInit/EndInit 对,在调用 EndInit 之前设置您需要的内容.

The BitmapImage BeginInit/EndInit pair ensures that you can set the settings you need at the same time and then during EndInit it executes. If you need to set any other properties, you should use the empty constructor and write out the BeginInit/EndInit pair like above, setting what you need before calling EndInit.

我通常也会设置这个选项,这会强制它在 EndInit 期间将图像加载到内存中:

I typically also set this option, which forces it to load the image into memory during EndInit:

image.CacheOption = BitmapCacheOption.OnLoad;

这将权衡可能更高的内存使用量以获得更好的运行时性能.如果这样做,则 BitmapImage 将在 EndInit 中同步加载,除非 BitmapImage 需要从 URL 异步下载.

This will trade off possible higher memory usage for better runtime performance. If you do this, then the BitmapImage will be loaded synchronously within EndInit, unless the BitmapImage requires async downloading from a URL.

进一步说明:

如果 UriSource 是绝对 Uri 并且是 http 或 https 方案,则 BitmapImage 将异步下载.您可以通过在 EndInit 之后检查 BitmapImage.IsDownloading 属性来判断它是否正在下载.有 DownloadCompleted、DownloadFailed 和 DownloadProgress 事件,但您必须更加棘手才能让它们在后台线程上触发.由于 BitmapImage 仅公开异步方法,因此您必须添加一个与 WPF 等效的 DoEvents() 的 while 循环,以保持线程处于活动状态,直到下载完成.此线程显示了有效的 DoEvents 代码在这个片段中:

BitmapImage will async download if the UriSource is an absolute Uri and is an http or https scheme. You can tell whether it is downloading by checking the BitmapImage.IsDownloading property after EndInit. There are DownloadCompleted, DownloadFailed, and DownloadProgress events, but you have to be extra tricky to get them to fire on the background thread. Since BitmapImage only exposes an asynchronous approach, you would have to add a while loop with the WPF equivalent of DoEvents() to keep the thread alive until the download is complete. This thread shows code for DoEvents that works in this snippet:

worker.DoWork += (s, e) =>
    {
        Uri uri = e.Argument as Uri;
        BitmapImage image = new BitmapImage();

        image.BeginInit();
        image.UriSource = uri;
        image.CacheOption = BitmapCacheOption.OnLoad;
        image.UriCachePolicy = new RequestCachePolicy(RequestCacheLevel.Default);
        image.EndInit();

        while (image.IsDownloading)
        {
            DoEvents(); //Method from thread linked above
        }
        image.Freeze();
        e.Result = image;
    };

虽然上述方法有效,但由于 DoEvents(),它有代码异味,并且它不允许您配置 WebClient 代理或其他可能有助于提高性能的东西.推荐上面的第一个例子而不是这个.

While the above approach works, it has a code smell because of DoEvents(), and it doesn't let you configure the WebClient proxy or other things that might help with better performance. The first example above is recommended over this one.

这篇关于在 WPF 的后台线程中加载图像的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆