ListView糟糕的滚动性能和冻结

时间:2017-09-21 20:36:03

标签: uwp windows-runtime win-universal-app windows-10-universal uwp-xaml

ListView VirtualizingStackPanel ItemsPanelTemplate <Image Source="{Binding Filename, Converter={StaticResource ImageConverter}}" /> 并且它拥有数百个缩略图(我知道它通常是糟糕的用户体验,但这是人们使用此应用程序的方式)

为了最大限度地减少内存使用量,我只保留模型中缩略图的路径,并在使用iValueConverter滚动到视图时将其转换为图像。

性能非常糟糕,使得用户界面无法响应,甚至在上下滚动后应用程序会冻结。

 public class ImageConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, string language)
        {
            string FileName = value as string;            
            var file = Windows.Storage.StorageFile.GetFileFromPathAsync(FileName).AsTask().Result;
            var stream = file.OpenReadAsync().AsTask().Result;
            var bitmapImage = new BitmapImage();
            bitmapImage.SetSource(stream);
            return bitmapImage;
        }

和转换器

IsAsync=true

有更好的方法吗?用户可以滚动一页,跳到中间,或滚动整个列表,所以我认为在模型中预加载BitmapImages是没有意义的。如果我这样做,滚动是顺利的,但应用程序内存占用量为1GB +,这是不可接受的。

WPF曾经有GRANT ALL ON insertarUsuario TO PUBLIC 用于绑定,但UWP无法使用。

1 个答案:

答案 0 :(得分:3)

问题是您在UI线程上执行了多次繁重的操作:

        var file = Windows.Storage.StorageFile.GetFileFromPathAsync(FileName).AsTask().Result;
        var stream = file.OpenReadAsync().AsTask().Result;
        var bitmapImage = new BitmapImage();
        bitmapImage.SetSource(stream);

如果您认为自己将快速滚动列表时出现的每个缩略图相乘,显然您会看到性能问题。该应用冻结了您可能因为您使用Task.Result阻止异步方法,这很容易导致deadlock。尽管SetSource方法不是异步,但它也很昂贵。

我认为您正在阅读信息流并从permissions concerns拨打SetSource。如果您可以直接将Image.Source绑定到路径字符串,那么这是目前最好的方法。

否则,您需要创建自定义行为以异步设置源。这是一个粗略的轮廓:

public static class ImageSourceStreamBehavior
{
    private static CancellationTokenSource GetCTS(DependencyObject obj)
    {
        return (CancellationTokenSource)obj.GetValue(CTSProperty);
    }

    private static void SetCTS(DependencyObject obj, CancellationTokenSource value)
    {
        obj.SetValue(CTSProperty, value);
    }

    public static readonly DependencyProperty CTSProperty =
        DependencyProperty.RegisterAttached("CTS", typeof(CancellationTokenSource), typeof(ImageSourceStreamBehavior), new PropertyMetadata(null));

    public static string GetSource(Image obj)
    {
        return (string)obj.GetValue(SourceProperty);
    }

    public static void SetSource(Image obj, string value)
    {
        obj.SetValue(SourceProperty, value);
    }

    public static readonly DependencyProperty SourceProperty =
        DependencyProperty.RegisterAttached("Source", typeof(string), typeof(ImageSourceStreamBehavior), new PropertyMetadata(null,
            (o, e) => OnSourceChanged((Image)o, (string)e.NewValue)));

    private static void OnSourceChanged(Image image, string newValue)
    {
        GetCTS(image)?.Cancel();
        var cts = new CancellationTokenSource();
        SetCTS(image, cts);

        SetSourceAsync(cts.Token, image, newValue);
    }

    private static async Task SetSourceAsync(CancellationToken ct, Image image, string fileName)
    {
        var file = await StorageFile.GetFileFromPathAsync(fileName).AsTask(ct);

        if (ct.IsCancellationRequested) { return; }

        var stream = await file.OpenReadAsync().AsTask(ct);

        if (ct.IsCancellationRequested) { return; }

        if (image.Source == null)
        {
            image.Source = new BitmapImage();
        }
        await ((BitmapImage)image.Source).SetSourceAsync(stream);
    }
}

由于列表中的项目被回收,因此如果图像在完全显示之前滚动出视图,则会取消应用前一图像。此外,您不再阻止UI线程。

您可以按如下方式使用它:

<Image ImageSourceStreamBehavior.Source="{Binding Filename}"/>

另外,正如Justin XL指出的那样,除非你有令人信服的理由,否则你应该使用ItemsStackpanel