异步加载 Web 图像
本文关键字:图像 Web 加载 异步 | 更新日期: 2023-09-27 18:34:21
我在 WPF 应用中显示来自 Web 的图像时遇到问题:
我为此任务使用了BitmapImage
。我的第一次尝试是在 UI 线程中执行它,但我很快就明白这是一个禁忌,因为应用程序在图像完全加载之前变得无响应。我的第二次尝试是使用BackgroudWorker
:
var worker = new BackgroundWorker();
worker.DoWork += worker_LoadImage;
worker.RunWorkerCompleted+=worker_RunWorkerCompleted;
worker.RunWorkerAsync(someURI);
和工作线程功能:
private void worker_LoadImage(object sender, DoWorkEventArgs e)
{
var image = new BitmapImage();
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnDemand;
image.UriSource = e.Argument as Uri;
image.DownloadFailed += new EventHandler<ExceptionEventArgs>(image_DownloadFailed);
image.EndInit();
e.Result = image;
}
void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//if I understand correctly, this code runs in the UI thread so the
//access to the component image1 is valid.
image1.Source = e.Result as BitmapImage;
}
在那之后,我仍然得到一个 InvalidOperationException:"调用线程无法访问此对象,因为另一个线程拥有它。我做了一些研究,发现由于BitmapImage
是Freezable
,因此在从另一个线程访问对象之前,我必须调用Freeze
。所以我试图将worker_LoadImage的最后一行替换为:
image.Freeze();
e.Result = image;
但是后来我得到了一个例外,我的图像无法冻结,我发现这可能是因为当我尝试调用Freeze()
时,图像没有下载完成。因此,我在映像创建中添加了以下代码:
image.DownloadCompleted += image_DownloadCompleted;
哪里:
void image_DownloadCompleted(object sender, EventArgs e)
{
BitmapImage img = (BitmapImage)sender;
img.Freeze();
}
所以现在我们到了真正的问题:如何让后台工作线程等到图像完全下载并触发事件?
我已经尝试了很多事情:在图像下载为真时循环,Thread.Sleep,Thread.Yield,信号量,事件等待句柄等等。我不知道图像下载实际上是如何在幕后工作的,但是当我尝试上述方法之一时会发生什么,图像永远不会完成下载(isDownloading卡在True上(
有没有更好、更简单的方法来完成我试图完成的相当简单的任务?
需要注意的一些事项:
这个答案实际上有效,但只有一次:当我尝试加载另一个图像时,它说调度程序已关闭。即使在阅读了一些关于调度员的信息之后,我也不真正了解 OP 是如何实现这一目标的,或者是否可以将解决方案扩展到多个图像。
当我在工作人员退出其 DoWork 功能之前放置一个消息框时,我单击"确定",图像显示,这意味着下载继续,同时在单击"确定"之前打开并完成消息框。
鉴于您正在使用位图的功能来异步加载图像,您首先不需要BackgroundWorker
。 与其创建 BGW 来启动异步操作并等待它完成,不如直接使用异步操作。
您所要做的就是从image_DownloadCompleted
处理程序更新 UI(冻结图像后(,您不再需要 BGW:
private void FetchImage(Uri uri)
{
var context = SynchronizationContext.Current;
var image = new BitmapImage();
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnDemand;
image.UriSource = uri;
image.DownloadFailed += image_DownloadFailed;
image.DownloadCompleted += (s, args) =>
{
image.Freeze();
context.Post(_ => image1.Source = image, null);
};
image.EndInit();
}