zoukankan      html  css  js  c++  java
  • 【.net深呼吸】WPF异步加载大批量图像

    如何在WPF中加载大批量数据,并且不会阻塞UI线程,尤其是加载大量图片时,这活儿一直是很多朋友都相当关注的。世上没有最完美的解决之道,咱们但求相对较优的方案。

    经过一些试验和对比,老周找到了一种算是不错的方案,重点是这个方案比较简单,无须闯五关斩六将,只要你对数据绑定有些基础就好了。

    好,F话少扯,咱们开始吧。

    老周手里没有那么多照片,那就用同一张图片做测试吧。假设我要在应用程序运行时加载 2 万张图片,我想2W张应该可以了,没见过谁会傻到要加载100W张那么变态。

    大致情况是:数据源集合是一个 ObservableCollection<Uri>, 也就是说集合中放的是图像的URI,为什么不放BitmapSource 呢,因为 DependencyObject 是不能跨线程操作的,只能在UI线程上创建。默认情况下,ObservableCollection<T>也不能在非UI线程上操作,不过,我可以通过调用以下方法来让它可以跨线程操作:

    public static void EnableCollectionSynchronization(IEnumerable collection, object lockObject)

    这个方法是 BindingOperations 类公开的静态方法,可以在窗口的构造函数中调用它,而且一定要在操作集合之前调用。调用时,把 ObservableCollection 集合传递给 collection 参数,第二个参数lockObject 是一个自定义对象,它指的是可以在线程间同步时引用的对象,在异步代码中,可以把这个对象写在一个 lock 语句块中。主要用途是防止UI访问集合的过程中,集合被其他线程意外修改。

    下面代码开启跨线程访问集合支持:

                images = new ObservableCollection<Uri>();
                ……
                lbImages.SetBinding(ItemsControl.ItemsSourceProperty, b);
    
                // 这一句很关键,开启集合的异步访问支持
                BindingOperations.EnableCollectionSynchronization(images, lockobj);

    然后在窗口的构造函数中,执行一个新 Task,用一个新线程来加载数据。

                Task.Run(() =>
                {
                    // 代码写在 lock 块中
                    lock (lockobj)
                    {
                        for (int i = 0; i < 20000; i++)
                        {
                            Uri u = new Uri("0.jpg", UriKind.Relative);
                            images.Add(u);
                        }
                    }
                });

    开始一个新Task是为了让主线程不受阻止,可以继续响应UI操作。

    由于集合中都是 URI,而界面上显示的是图像,可以弄一个自定义的数据转换器,转换为位图。

        public sealed class UriToBitmapConverter : IValueConverter
        {
            public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
            {
                Uri uri = (Uri)value;
                BitmapImage bmp = new BitmapImage();
                bmp.DecodePixelHeight = 250; // 确定解码高度,宽度不同时设置
                bmp.BeginInit();
                // 延迟,必要时创建
                bmp.CreateOptions = BitmapCreateOptions.DelayCreation;
                bmp.CacheOption = BitmapCacheOption.OnLoad;
                bmp.UriSource = uri;
                bmp.EndInit(); //结束初始化
                return bmp;
            }
    
            public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
            {
                return null;
            }
        }

    因为是单向转换,所以ConvertBack就免了。

    注意,在实例化BitmapImage时,DecodePixelHeight 和 DecodePixelWidth 属性只能设置任意一个,不要同时设置,不然图片的比例会变形。如果我们界面用的图不需要很大,就设一个小的值,比如200像素,这样可以节约性能。

    还可以把 CreateOptions 属性设为 DelayCreation ,这样只在图像需要时才会创建,也省了一些性能。

    为了让这个转换器能在XAML代码中访问,需要把它的实例声明在UI的资源列表中。

            <Grid.Resources>
                <local:UriToBitmapConverter x:Key="tobmpcvt"/>
            </Grid.Resources>

    接下来就是用Binding了,实现界面绑定。

            <ListBox Name="lbImages" ScrollViewer.IsDeferredScrollingEnabled="False"
                     ScrollViewer.HorizontalScrollBarVisibility="Disabled">
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <Image Height="200" Width="200" Source="{Binding IsAsync=True,Converter={StaticResource tobmpcvt}}"/>
                    </DataTemplate>
                </ListBox.ItemTemplate>
                <ListBox.ItemsPanel>
                    <ItemsPanelTemplate>
                        <WrapPanel Orientation="Horizontal"/>
                    </ItemsPanelTemplate>
                </ListBox.ItemsPanel>
            </ListBox>

    使用 Binding 时,把 IsAsync 属性设为 True,这样允许界面使用辅助线程来绑定数据,记得,记得。

    这样就完成了,然后我们可以运行,让程序加载 2万个图像。这时候会发现,程序运行后不会卡住了,而且把滚动往下拖动时,会自动加载数据。

    如何?这效果不错吧。

    示例源代码下载地址

  • 相关阅读:
    斐波那契数列 的两种实现方式(Java)
    单链表反转
    单链表合并
    两个有序list合并
    list去重 转载
    RemoveAll 要重写equals方法
    Java for LeetCode 138 Copy List with Random Pointer
    Java for LeetCode 137 Single Number II
    Java for LeetCode 136 Single Number
    Java for LeetCode 135 Candy
  • 原文地址:https://www.cnblogs.com/tcjiaan/p/5532104.html
Copyright © 2011-2022 走看看