RadListBox 上的 ScrollView (ScrollInfo) Action 导致内存泄漏

问题描述 投票:0回答:1

[在此输入图像描述][1]我向RadListBox添加一个项目并始终更新它以实时记录程序的运行状态

向列表框添加项目不是问题,但自从我添加了更改 SelectedItem 的语法后,就发生了内存泄漏。

根据诊断工具,有“Action”对象和wpf的“scrollInfo”类型的内存积累,但我不知道具体是什么

这不是一个比任务的处理速度更快发生的问题

如果使用Winform ListBox而不是wpf,或者不更新SelectedItem,则不会发生这种类型的内存泄漏。(即使使用Wpf)

仅在 TAB 期间出现在可见列表框中。

    delegate void DInsertList(RadListBox nlistbox, string str);
    public static void InsertList(RadListBox nlistbox, string str)
    {
        if (nlistbox.Dispatcher.Thread == Thread.CurrentThread)
        {
            if (str != null)
            {
                if (nlistbox.Items.Count > 100)
                {
                  nlistbox.Items.RemoveAt(0);
                }
                
                nlistbox.Items.Add(str);
         //       nlistbox.SelectedIndex = (int )(nlistbox.Items.Count - 1);
                nlistbox.SelectedItem = nlistbox.Items[(int)(nlistbox.Items.Count - 1)];
            }
        }
        else
        {

            DInsertList call = new DInsertList(InsertList);
            nlistbox.Dispatcher.BeginInvoke(call, nlistbox, str);
        }
    }
wpf
1个回答
0
投票

这不是内存泄漏。这只是内存分配。如果垃圾收集器无法释放分配的内存,则会发生泄漏。
您所看到的是从虚拟化

ListBox
(或扩展 WPF
RadListBox
ListBox
)中删除可见项容器的效率低下。

ListBox
使用UI虚拟化。此功能允许仅创建和加载(实现)可见项目容器。
如果不启用 UI 虚拟化,将创建所有项目容器。

现在,当您从集合中删除索引不是最后一个的项目时,此操作会导致项目重新排序。例如,删除第一个项目将影响整个集合,因为它会更改每个项目的索引。

如果此更改影响已实现的项目容器,则

ListBox
被迫重新创建视图,这将导致创建新的项目容器实例(例如
ListBoxItem
)以及布局引擎的完整传递以呈现新的容器。
内存分析器通过显示分配内存的增加(对于新容器)来反映这一点。
IScrollInfo
VirtualizingStackPanel
底层的
ListBox
实现,并根据当前滚动位置控制项目容器的实际实现。当然,重新创建视图时会涉及这种类型,因为它实际上决定什么是可见的,什么是不可见的。

除非错误的项目容器处理导致引用保持活动状态,否则垃圾收集器将在一段时间后释放旧项目容器的内存。

但是,当启用 UI 虚拟化时,删除/替换可见项对整体性能的影响非常低。

您可以通过配置

ListBox
来回收其容器来进一步减少影响。这样,项目容器就不会每次都重新创建,并且
ListBox
可以重用它们:

<ListBox VirtualizingPanel.VirtualizationMode="Recycling" />

解决您的问题

从您发布的代码中我可以看出您正在显式添加

ListBoxItems
(因为通过
ListBox
属性修改
Items
否则会导致异常)。
如果不使用
ItemsSource
属性来填充
ListBox
,您将默默地禁用 UI 虚拟化功能,如前所述。

在这些情况下,删除项目对性能的影响非常高,因为此操作会强制

ListBox
丢弃 所有 项目容器,而不是仅丢弃可见的容器。
内存使用量激增,用户界面会感觉迟缓甚至冻结(一切都取决于项目的数量 - 项目越多,影响就越大)。

因此,您必须始终通过

ItemsControl
类型的源集合修改
ObservableCollection

您可以通过删除冗余并使用适当的 API 来进一步改进您发布的代码。

建议的修复如下所示:

MainWindow.xaml.cs

public partial class MainWindow : Window
{
  public ObservableCollection<string> Values
  {
    get => (ObservableCollection<string>)GetValue(ValuesProperty);
    set => SetValue(ValuesProperty, value);
  }

  public static readonly DependencyProperty ValuesProperty = DependencyProperty.Register(
    "Values", 
    typeof(ObservableCollection<string>), 
    typeof(MainWindow), 
    new PropertyMetadata(default));

  public string SelectedValue
  {
    get => (string)GetValue(SelectedValueProperty);
    set => SetValue(SelectedValueProperty, value);
  }

  public static readonly DependencyProperty SelectedValueProperty = DependencyProperty.Register(
    "SelectedValue",
    typeof(string),
    typeof(MainWindow),
    new PropertyMetadata(default));

  public MainWindow()
  {
    InitializeComponent();

    // Initialize with "A" to "Z"
    IEnumerable<string> textValues = Enumerable.Range(65, 26).Select(value => new string(new[] { (char)value }));
    this.Values = new ObservableCollection<string>(textValues);
  }

  // Consider making this an instance method. 
  // Global class methods usually lead to bad design.
  public static void InsertList(string str)
  {   
    // Alternative use Application.Current.Dispatcher 
    // if you are not in the scope of a DispatcherObject
    if (this.Dispatcher.CheckAccess())
    {
      if (str != null)
      {
        if (this.Values.Count > 100)
        {
          this.Values.RemoveAt(0);
        }

        // Adding an item automatically inserts it at the end...
        this.Values.Add(str);

        // ...that's why we can avoid looking up the last item...
        //nlistbox.SelectedItem = nlistbox.Items[(int)(nlistbox.Items.Count - 1)];
   
        // ...and simply use the reference of the currently added item 'str'
        this.SelectedValue = str;
      }
    }
    else
    {
      this.Dispatcher.InvokeAsync(() => InsertList(str));
    }
  }
}

MainWIndow.xaml

<Window x:Name="Root">

  <!-- 
       Now that the list is populated using the ItemsSource, 
       UI virtualization is enabled, which will significantly improve 
       the performance of the ListBox (in terms of memory and rendering speed).
       Configuring the control to recycle item containers will 
       further improve the performance.
  -->
  <RadListBox VirtualizingPanel.VirtualizationMode="Recycling" 
              ItemsSource="{Binding ElementName=Root, Path=Values}"
              SelectedItem="{Bidning ElementName=Root, Path=SelectedItem}" />
</Window>
© www.soinside.com 2019 - 2024. All rights reserved.