我已经采用了几种方法来解决这个问题,包括使用WebClient和BitmapImage。
编辑:原始建议是使用BitmapImage(Uri,RequestCachePolicy)构造函数,但我意识到我测试这种方法的项目只使用本地文件,而不是网络。更改指导以使用我测试过的其他网络技术。
您应该在后台线程上运行下载和解码,因为在加载期间,无论是同步还是在下载图像之后,都需要一定的时间来解码图像。如果您正在加载许多图像,则可能会导致UI线程停顿。(这里还有一些其他复杂性,比如DelayCreation,但它们不适用于您的问题。)
有几种加载图像的方法,但我发现在BackgroundWorker中从网络加载时,您需要使用WebClient或类似的类自己下载数据。
请注意,BitmapImage在内部使用WebClient,还具有许多错误处理和设置凭据和其他事项的功能,我们必须针对不同情况进行调整。我提供这个片段,但它只在有限的情况下进行了测试。如果您涉及代理,凭据或其他情况,则必须稍微修改此内容。
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += (s, e) =>
{
Uri uri = e.Argument as Uri;
using (WebClient webClient = new WebClient())
{
webClient.Proxy = null;
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)
{
e.Result = ex;
}
}
};
worker.RunWorkerCompleted += (s, e) =>
{
BitmapImage bitmapImage = e.Result as BitmapImage;
if (bitmapImage != null)
{
myImage.Source = bitmapImage;
}
worker.Dispose();
};
worker.RunWorkerAsync(imageUri);
我在一个简单的项目中测试过,它可以正常工作。我不确定它是否正在访问缓存,但从我从MSDN、其他论坛问题和对PresentationCore的反射中得出的结论来看,它应该正在访问缓存。WebClient包装了WebRequest,后者包装了HTTPWebRequest,以此类推,并且缓存设置会传递到每个层级。
BitmapImage的BeginInit/EndInit配对确保您可以同时设置所需的设置,然后在EndInit期间执行。如果您需要设置任何其他属性,您应该使用空构造函数并编写像上面那样的BeginInit/EndInit配对,在调用EndInit之前设置所需内容。
我通常还设置此选项,强制在EndInit期间将图像加载到内存中:
image.CacheOption = BitmapCacheOption.OnLoad
这将以更好的运行时性能为代价来换取可能更高的内存使用。如果您这样做,那么除非BitmapImage需要通过URL进行异步下载,否则它将在EndInit中同步加载。
进一步说明:
如果UriSource是绝对Uri并且是http或https方案,则BitmapImage将会异步下载。您可以在EndInit后检查BitmapImage.IsDownloading属性来判断它是否正在下载。有DownloadCompleted、DownloadFailed和DownloadProgress事件,但你必须特别巧妙地让它们在后台线程上触发。因为BitmapImage只公开了一种异步方法,所以你必须添加一个while循环和WPF等效的DoEvents()来保持线程活动,直到下载完成。
此线程显示了在此片段中有效的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();
}
image.Freeze();
e.Result = image;
};
虽然上述方法可以运行,但由于DoEvents()的存在,它会产生代码气味,并且不允许您配置WebClient代理或其他可能有助于更好性能的东西。建议使用上面的第一个示例。