WPF UI 加载图像时的性能影响

WPF UI performance impact when loading images

我一直在研究如何最大限度地减少对 binding/loading 图像对列表的性能影响。

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

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

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

and/or 与标记为异步的主要源的优先绑定(当我单独使用这种方法时,我没有看到任何改进,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 当显示实际图像时(在最后一行)。当你有一个很好的最后图像列表时,冻结会更明显。

有办法解决这个问题吗?我想要的只是 UI 在图像显示时仍会响应...

您的代码有一些问题。您不得在 ImageUrl 属性 的 setter 中调用 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;
            }
        });
    }
}