异步加载Web图像

时间:2014-12-16 20:36:36

标签: c# wpf backgroundworker dispatcher bitmapimage

我在WPF应用中遇到了从网络上显示图片的问题:

我使用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:&#34;调用线程无法访问此对象,因为另一个线程拥有它。&#34; 我进行了一些研究,发现由于BitmapImageFreezable,我必须在从另一个线程访问对象之前调用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();
 }

所以现在我们回到真正的问题: 如何让后台工作人员等到图像完全下载并触发事件?

我尝试了很多东西:在图像的isDownloading为true时循环,Thread.Sleep,Thread.Yield,Semaphores,Event wait handle等等。 我不知道图像下载实际上是如何在幕后工作但当我尝试上述方法之一时会发生的情况是图像永远不会完成下载(isDownloading被卡在True上)

有没有更好,更简单的方法来实现我想要完成的相当简单的任务?

有些事情需要注意:

  1. this answer实际上有效,但只有一次:当我尝试加载另一张图片时,它说调度程序已关闭。即使在阅读了一些关于Dispatchers的内容之后,我也不能真正理解OP是如何实现这一目标的,或者是否可以为多个图像扩展解决方案。

  2. 当我在工作人员退出其DoWork功能之前放置一个消息框时,我单击确定并且图像会显示,这意味着在单击确定之前打开并完成消息框后继续下载。

    < / LI>

1 个答案:

答案 0 :(得分:1)

鉴于您正在使用位图的异步加载图像的能力,您首先不需要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();
}