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万个图像。这时候会发现,程序运行后不会卡住了,而且把滚动往下拖动时,会自动加载数据。

    如何?这效果不错吧。

    示例源代码下载地址

  • 相关阅读:
    Asp.Net MVC4开发二: Entity Framework在Asp.Net MVC4中的应用
    敌兵布阵(杭电1166)(树状数组)
    alibaba dexposed初步解析
    shell学习三十二天----read读取一行
    cocos2d-x CCScrollView 源代码分析
    语言-编程语言:Python
    GitHub:Python
    GitHub-Microsoft:DotNet4
    GitHub-Microsoft:DotNet3
    GitHub-Microsoft:DotNet2
  • 原文地址:https://www.cnblogs.com/tcjiaan/p/5532104.html
Copyright © 2011-2022 走看看