【问题标题】:Make WPF Image load async使 WPF 图像加载异步
【发布时间】:2013-04-08 18:07:18
【问题描述】:

我想加载 Gravatar-Images 并将它们从后面的代码设置为 WPF Image-Control。 所以代码看起来像

imgGravatar.Source = GetGravatarImage(email);

GetGravatarImage 的样子:

BitmapImage bi = new BitmapImage();
bi.BeginInit();
bi.UriSource = new Uri( GravatarImage.GetURL( "http://www.gravatar.com/avatar.php?gravatar_id=" + email) , UriKind.Absolute );
bi.EndInit();
return bi;

不幸的是,当网络连接缓慢时,这会锁定 GUI。有没有办法分配图像源并让它在后台加载图像而不阻塞 UI?

谢谢!

【问题讨论】:

    标签: wpf image asynchronous


    【解决方案1】:

    我建议您对来自 XAML 的 imgGravatar 使用绑定。在其上设置 IsAsync=true,WPF 将自动利用线程池中的线程来拉取您的图像。您可以将解析逻辑封装到 IValueConverter 中,然后简单地将电子邮件绑定为 Source

    在 XAML 中:

    <Window.Resouces>
        <local:ImgConverter x:Key="imgConverter" />
    </Window.Resource>
    
    ...
    
    
    <Image x:Name="imgGravatar" 
           Source="{Binding Path=Email, 
                            Converter={StaticResource imgConverter}, 
                            IsAsync=true}" />
    

    在代码中:

    public class ImgConverter : IValueConverter
    {
        public override object Convert(object value, ...)
        {
            if (value != null)
            {
                 BitmapImage bi = new BitmapImage();
                 bi.BeginInit();
                 bi.UriSource = new Uri( 
                     GravatarImage.GetURL(
                         "http://www.gravatar.com/avatar.php?gravatar_id=" + 
                          value.ToString()) , UriKind.Absolute 
                     );
                 bi.EndInit();
                 return bi;                
            }
            else
            {
                return null;
            }
    
        }
    }
    

    【讨论】:

    • BitmapImage 也有一个constructor with Uri parameter,这样可以节省 BeginInit/EndInit 调用。
    • +1 不知道,很好的提示。我只是从作者那里复制了代码,并没有考虑如何优化实际下载,而是专注于以一种优雅的方式解决 UI 阻塞问题(当然,如果代码不是异步的,它会阻塞)。我认为对于重复使用,转换器还可以从绑定中获取完整的 url,以便更可重用,而不是电子邮件。我不喜欢使用ThreadPool,因为你必须一直管理调度。对于纯 MVVM 应用程序,这对我来说不够优雅。
    • 你能解释一下为什么在不异步调用时创建 BitmapImage 会阻塞。那么为什么它有IsDownloading 属性和DownloadCompleted 事件。如果您只是将 XAML 中的 Image 控件的 Source 属性设置为引用 Web 上的大图像的 URL,则 UI 不会阻塞。相反,WPF 在后台下载图像并在下载完成后立即显示。
    • 我很确定这是由 BitmapImage 执行的,因为(来自 MSDN)“BitmapImage 的存在主要是为了支持可扩展应用程序标记语言 (XAML) 语法,并为位图加载引入了其他属性,这些属性不是由 BitmapSource 定义”。
    • 我用一个巨大的图像(14MB)测试了一下,你是绝对正确的。下载由BitmapImage 在后台线程上自动执行。 UI 仅在将图像数据加载到 UI 时挂起。对困惑感到抱歉。在BitmapImage 分配为Source 而不是BeginInit() 之后开始下载。所以基本上作者的原始代码应该可以工作。所以也许这与他在 UI 上使用的图像数量有关。
    【解决方案2】:

    我不明白为什么您的代码会阻止 UI,因为 BitmapImage 支持在后台下载图像数据。这就是为什么它有一个IsDownloading 属性和一个DownloadCompleted 事件。

    无论如何,以下代码显示了一种完全在单独线程(来自ThreadPool)中下载和创建图像的简单方法。它使用WebClient 实例下载整个图像缓冲区,然后从该缓冲区创建 BitmapImage。创建 BitmapImage 后,它会调用 Freeze 以使其可从 UI 线程访问。最后,它通过 Dispatcher.BeginInvoke 调用在 UI 线程中分配 Image 控件的 Source 属性。

    ThreadPool.QueueUserWorkItem(
        o =>
        {
            var url = GravatarImage.GetURL(
               "http://www.gravatar.com/avatar.php?gravatar_id=" + email);
            var webClient = new WebClient();
            var buffer = webClient.DownloadData(url);
            var bitmapImage = new BitmapImage();
    
            using (var stream = new MemoryStream(buffer))
            {
                bitmapImage.BeginInit();
                bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
                bitmapImage.StreamSource = stream;
                bitmapImage.EndInit();
                bitmapImage.Freeze();
            }
    
            Dispatcher.BeginInvoke((Action)(() => image.Source = bitmapImage));
        });
    

    编辑:今天你将只使用异步方法:

    var url = GravatarImage.GetURL(
        "http://www.gravatar.com/avatar.php?gravatar_id=" + email);
    var httpClient = new HttpClient();
    var responseStream = await httpClient.GetStreamAsync(url);
    var bitmapImage = new BitmapImage();
    
    using (var memoryStream = new MemoryStream())
    {
        await responseStream.CopyToAsync(memoryStream);
    
        bitmapImage.BeginInit();
        bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
        bitmapImage.StreamSource = memoryStream;
        bitmapImage.EndInit();
        bitmapImage.Freeze();
    }
    
    image.Source = bitmapImage;
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-05-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-05-29
      相关资源
      最近更新 更多