【问题标题】:WPF UI performance impact when loading images加载图像时 WPF UI 性能影响
【发布时间】:2022-01-24 03:09:57
【问题描述】:

我一直在寻找将图像绑定/加载到列表的性能影响降到最低的方法。

我尝试了一些人们在网上建议的不同选项,例如:

https://social.msdn.microsoft.com/Forums/en-US/7fc238ea-194e-4f29-bcbd-9a3d4bdb2180/async-loading-of-bitmapsource-in-value-converter?forum=wpf

或使用单独的线程将图像列表加载到 ImageSource 中,然后绑定到列表视图

和/或与标记为异步的主要源的优先级绑定(当我单独使用这种方法时,我没有看到任何改进,tbh)

他们都有一定程度的改进,我最终得到了如下内容: 具有异步加载功能的自定义图像控件

public class AsyncImage : Image
{
    private static ImageSource _blank;
    private static Random random;
    private static ImageSource Blank 
    {
        get
        {
            if(_blank == null)
            {
                var bi = new BitmapImage();
                bi.BeginInit();
                Stream imgStream = File.OpenRead("D:\\...\\blankLoading.png");
                bi.StreamSource = imgStream;
                bi.EndInit();
                bi.Freeze();
                _blank = bi;
            }
            return _blank;
        }
    }
    public string ImageUrl
    {
        get { return GetValue(ImageUrlProperty).ToString(); }
        set 
        { 
            SetValue(ImageUrlProperty, value);
            LoadImageAsync(value);
        }
    }
    public static readonly DependencyProperty ImageUrlProperty =
       DependencyProperty.Register("ImageUrl", typeof(string), typeof(AsyncImage), new UIPropertyMetadata(string.Empty, new PropertyChangedCallback(OnImageUrlChanged)));

    private static void OnImageUrlChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        AsyncImage control = (AsyncImage)d;
        control.ImageUrl = e.NewValue.ToString();
    }
    private void LoadImageAsync(string url)
    {
        Image image = new Image();
        base.Source = Blank; 

        ThreadPool.QueueUserWorkItem((r) =>
        {
          
            BitmapImage bi = null;
            bi = new BitmapImage();
            bi.BeginInit();
            var bytes = File.ReadAllBytes(url); //i want to make sure data is loaded before assignment
            bi.StreamSource = new MemoryStream(bytes);
            bi.EndInit();
            bi.Freeze(); //makes sure your image can be passed across threads
            Console.Write(url);
            image.Dispatcher.Invoke(DispatcherPriority.Normal,
                (ThreadStart)delegate
                {
                    base.Source = bi; //this is where it actually comes back to UI thread
                });
        });

    }
}

使用这种方法,我可以将 URL 列表或文件路径绑定到列表视图,并且数据绑定将执行得更加流畅......但是,当实际图像是已显示(在最后一行)。当您有一个很好的最后图像列表时,冻结会更加明显。

有没有办法解决这个问题?我想要的是,当图像显示时,UI 仍然可以响应......

【问题讨论】:

  • 你是否在这段代码中正确地处理了所有地方的资源?如果碰巧在任何地方留下了未处理的资源,那么这可能会成为临时内存占用,我想这可能会减慢执行速度。只是一个想法。
  • 你是对的@BentTranberg,好点。一旦我解决了问题并整理了代码,我就会这样做。在这个阶段不太确定我的代码。

标签: c# wpf image data-binding .net-framework-4.8


【解决方案1】:

您的代码有一些问题。您不能在 ImageUrl 属性的设置器中调用 LoadImageAsync()。该方法必须改为在 OnImageUrlChanged 中调用。

在 LoadImageAsync 中,您创建一个 Image 元素只是为了使用它的 Dispatcher,这完全没有意义。使用自定义控件的 Dispatcher,例如 this.Dispatcher。还要写this.Source而不是base.Source

您还必须关闭加载 BitmapImage 的流。设置 bi.CacheOption = BitmapCacheOption.OnLoad 并通过 using 块处理 MemoryStream。

也就是说,考虑使用不带ThreadPool.QueueUserWorkItemDispatcher.Invoke 的更现代的实现。

改用Task.Run

public class AsyncImage : Image
{
    public static readonly DependencyProperty ImagePathProperty =
        DependencyProperty.Register(
            nameof(ImagePath), typeof(string), typeof(AsyncImage),
            new PropertyMetadata(async (o, e) =>
                await ((AsyncImage)o).LoadImageAsync((string)e.NewValue)));

    public string ImagePath
    {
        get { return (string)GetValue(ImagePathProperty); }
        set { SetValue(ImagePathProperty, value); }
    }

    private async Task LoadImageAsync(string imagePath)
    {
        Source = await Task.Run(() =>
        {
            using (var stream = File.OpenRead(imagePath))
            {
                var bi = new BitmapImage();
                bi.BeginInit();
                bi.CacheOption = BitmapCacheOption.OnLoad;
                bi.StreamSource = stream;
                bi.EndInit();
                bi.Freeze();
                return bi;
            }
        });
    }
}

【讨论】:

  • 非常感谢您指出我的错误和代码,这很有魅力……所以根据您提出的第一点,我实际上还在调用主线程上的负载吗?跨度>
  • 不,除了属性设置器中的 SetValue 调用之外,不能有其他任何东西。我认为您的性能问题是由于多余的 Image 元素造成的。
  • 这是一个很好的答案。
猜你喜欢
  • 2011-12-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-05-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多