[英]Loading an image in a background thread in 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. 在这个网站和其他论坛上已经有很多关于这方面的问题,但我还没有找到真正有效的解决方案。
Here's what I want to do: 这就是我想要做的事情:
Things I have tried: 我尝试过的事情:
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? 你怎么看?
I have approached this problem in several ways, including with WebClient and just with BitmapImage. 我已经通过多种方式解决了这个问题,包括WebClient和BitmapImage。
EDIT: 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. 编辑:原始建议是使用BitmapImage(Uri,RequestCachePolicy)构造函数,但我意识到我测试此方法的项目只使用本地文件,而不是web。 Changing guidance to use my other tested web technique. 更改指南以使用我的其他测试网络技术。
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. 如果要加载许多图像,则可能导致UI线程停止。 (There are a few other intricacies here like DelayCreation but they don't apply to your question.) (这里有一些其他复杂的东西,比如DelayCreation,但它们不适用于你的问题。)
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. 有几种方法可以加载图像,但我发现在BackgroundWorker中从Web加载时,您需要使用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. 请注意,BitmapImage内部使用WebClient,而且它有很多错误处理和凭据设置以及我们必须针对不同情况确定的其他事项。 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);
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. 我不是100%关于它是否正在达到缓存,而是从我从MSDN可以看出的,其他论坛问题,以及Reflectoring到PresentationCore它应该打到缓存。 WebClient wraps WebRequest, which wraps HTTPWebRequest, and so on, and the cache settings are passed down each layer. WebClient包装WebRequest,它包装HTTPWebRequest,依此类推,缓存设置在每一层传递下来。
The BitmapImage BeginInit/EndInit pair ensures that you can set the settings you need at the same time and then during EndInit it executes. BitmapImage BeginInit / EndInit对确保您可以同时设置所需的设置,然后在执行EndInit期间。 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. 如果你需要设置任何其他属性,你应该使用空构造函数并写出如上所述的BeginInit / EndInit对,在调用EndInit之前设置你需要的东西。
I typically also set this option, which forces it to load the image into memory during EndInit: 我通常还设置此选项,强制它在EndInit期间将图像加载到内存中:
image.CacheOption = BitmapCacheOption.OnLoad;
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. 如果这样做,那么BitmapImage将在EndInit中同步加载,除非BitmapImage需要从URL下载异步。
Further notes: 附加说明:
BitmapImage will async download if the UriSource is an absolute Uri and is an http or https scheme. 如果UriSource是绝对Uri并且是http或https方案,则BitmapImage将异步下载。 You can tell whether it is downloading by checking the BitmapImage.IsDownloading property after EndInit. 您可以通过在EndInit之后检查BitmapImage.IsDownloading属性来判断它是否正在下载。 There are DownloadCompleted, DownloadFailed, and DownloadProgress events, but you have to be extra tricky to get them to fire on the background thread. 有DownloadCompleted,DownloadFailed和DownloadProgress事件,但是你必须要特别棘手才能在后台线程上触发它们。 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. 由于BitmapImage仅公开异步方法,因此您必须使用等效于DoEvents()的WPF添加while循环,以使该线程保持活动状态,直到下载完成为止。 This thread shows code for DoEvents that works in this snippet: 此线程显示适用于此代码段的DoEvents的代码:
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;
};
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. 虽然上述方法有效,但由于DoEvents(),它具有代码味道,并且它不允许您配置WebClient代理或其他可能有助于提高性能的事物。 The first example above is recommended over this one. 建议使用上面的第一个示例。
The BitmapImage needs async support for all of its events and internals. BitmapImage需要对其所有事件和内部的异步支持。 Calling Dispatcher.Run() on the background thread will...well run the dispatcher for the thread. 在后台线程上调用Dispatcher.Run()将...运行该线程的调度程序。 (BitmapImage inherits from DispatcherObject so it needs a dispatcher. If the thread that created the BitmapImage doesn't already have a dispatcher a new one will be created on demand. cool.). (BitmapImage继承自DispatcherObject,因此它需要一个调度程序。如果创建BitmapImage的线程还没有调度程序,则将根据需要创建一个新的调度程序。很酷。)。
Important safety tip: The BitmapImage will NOT raise any events if it is pulling data from cache (rats). 重要安全提示:如果BitmapImage从缓存(大鼠)中提取数据,则不会引发任何事件。
This has been working very well for me.... 这对我来说非常好....
var worker = new BackgroundWorker() { WorkerReportsProgress = true };
// DoWork runs on a brackground thread...no thouchy uiy.
worker.DoWork += (sender, args) =>
{
var uri = args.Argument as Uri;
var image = new BitmapImage();
image.BeginInit();
image.DownloadProgress += (s, e) => worker.ReportProgress(e.Progress);
image.DownloadFailed += (s, e) => Dispatcher.CurrentDispatcher.InvokeShutdown();
image.DecodeFailed += (s, e) => Dispatcher.CurrentDispatcher.InvokeShutdown();
image.DownloadCompleted += (s, e) =>
{
image.Freeze();
args.Result = image;
Dispatcher.CurrentDispatcher.InvokeShutdown();
};
image.UriSource = uri;
image.EndInit();
// !!! if IsDownloading == false the image is cached and NO events will fire !!!
if (image.IsDownloading == false)
{
image.Freeze();
args.Result = image;
}
else
{
// block until InvokeShutdown() is called.
Dispatcher.Run();
}
};
// ProgressChanged runs on the UI thread
worker.ProgressChanged += (s, args) => progressBar.Value = args.ProgressPercentage;
// RunWorkerCompleted runs on the UI thread
worker.RunWorkerCompleted += (s, args) =>
{
if (args.Error == null)
{
uiImage.Source = args.Result as BitmapImage;
}
};
var imageUri = new Uri(@"http://farm6.static.flickr.com/5204/5275574073_1c5b004117_b.jpg");
worker.RunWorkerAsync(imageUri);
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.