【问题标题】:Loading a web image asynchronously异步加载 Web 图像
【发布时间】:2014-12-16 20:36:36
【问题描述】:

我在 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:“调用线程无法访问此对象,因为不同的线程拥有它。” 我研究了一下,发现由于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、信号量、事件等待句柄等等。 我不知道图片下载实际上是如何在幕后工作的,但是当我尝试上述方法之一时会发生什么情况是图片永远不会完成下载(isDownloading 卡在 True 上)

有没有更好、更简单的方法来完成我试图完成的相当简单的任务?

注意事项:

  1. this answer 确实有效,但只有一次:当我尝试加载另一个图像时,它说调度程序已关闭。即使在阅读了一些关于 Dispatchers 的内容后,我也不太了解 OP 是如何实现这一点的,或者是否可以将解决方案扩展到多个图像。

  2. 当我在工作人员退出他的 DoWork 函数之前放置一个消息框时,我单击“确定”并出现图像,这意味着在我单击“确定”之前打开并完成消息框时继续下载。

【问题讨论】:

  • BitmapImage 已经异步加载图像。您需要做的就是image1.Source = new BitmapImage(someURI);。要在单独的线程中执行整个图像加载,请参阅this answer
  • 像魅力一样工作!非常感谢。

标签: c# wpf backgroundworker dispatcher bitmapimage


【解决方案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();
}

【讨论】:

  • 我觉得自己好蠢。。谢谢!我回去检查:第一个 BitmapImage 被阻塞了,使整个屏幕冻结了几秒钟,但其他图像确实可以顺利异步加载。有没有理由会发生这样的事情?有什么办法可以预防吗?
  • @Block 它在哪里阻塞?
  • 我不是 100% 确定,因为我使用消息框来查看冻结的位置,但我认为它发生在:image.EndInit();行
  • @Block 我不确定是什么原因造成的,但您可以在调用var context ... 之后将所有内容包装到Task.Run 并在另一个线程中运行任务创建代码,如果它由于某种原因阻塞了 UI。这有点小技巧,但如果这是问题所在,那么它就可以正常工作。
  • 我不确定我是否完全理解你的意思(对不起,我对此很陌生)我尝试将所有内容放在 Task.Run 中的上下文声明之后,但显然这不起作用,因为新线程和其中的图像声明在下载完成之前被销毁。你能解释一下你的意思吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-12-10
  • 2011-12-23
  • 2013-04-08
相关资源
最近更新 更多