简体   繁体   English

WPF DataGrid.items.Refresh() memory 泄漏

[英]WPF DataGrid.items.Refresh() memory leak

Good afternoon.下午好。 Help in solving the following problem.帮助解决以下问题。 If I set some DataTable for DataGrid.ItemsSource , which I will update in another thread, then periodically calling DataGrid.Items.Refresh() I will have a memory leak.如果我为DataGrid.ItemsSource设置了一些DataTable ,我将在另一个线程中对其进行更新,然后定期调用DataGrid.Items.Refresh()我将有 memory 泄漏。 Is there a way to avoid this?有没有办法避免这种情况?

<Window x:Class="TestDataGrid.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:TestDataGrid"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition Width="7*"/>
        </Grid.ColumnDefinitions>
        <DataGrid x:Name="DataGrid1" d:ItemsSource="{d:SampleData ItemCount=5}" Grid.ColumnSpan="2"/>

    </Grid>
</Window>

MainWindow主窗口

using System;
using System.Windows;
namespace TestDataGrid
{    
    public partial class MainWindow : Window
    {
        DataWorker data = new DataWorker();
        public MainWindow()
        {
            InitializeComponent();            
            DataGrid1.ItemsSource = data.dt.DefaultView;
            data.DataChanged += OnDataChanged;
            data.RunThread();
        }

        private void OnDataChanged(object obj, EventArgs e)
        {
            Dispatcher.Invoke(new Action(() =>
            {
                DataGrid1.Items.Refresh();
                //this.Title = data.dt.Rows[0].ItemArray[0].ToString();
            }));
        }
    }
}

DataWorker.cs数据工作者.cs

using System;
using System.Data;
using System.Threading;

namespace TestDataGrid
{
    internal class DataWorker
    {
        public event EventHandler DataChanged;
        public DataWorker()
        {
            column.DataType = Type.GetType("System.UInt64");
            column.ColumnName = "DATA";
            dt.Columns.Add(column);
        }

        public DataTable dt = new DataTable();
        private DataColumn column = new DataColumn();        

        public void RunThread()
        {
            Thread th = new Thread(DataChange);
            th.Start();
        }
        private void DataChange()
        {
            while (true)
            {
                dt.Clear();
                dt.Rows.Add(new object[] { DateTime.Now.Ticks });
                DataChanged?.Invoke(this, EventArgs.Empty);
                Thread.Sleep(1000);
            }
                
        }

    }
}

If I change DataGrid1.Items.Refresh() to this.Title = data.dt.Rows[0].ItemArray[0].ToString() for exemple, then memory OK.例如,如果我将DataGrid1.Items.Refresh()更改为this.Title = data.dt.Rows[0].ItemArray[0].ToString() ,则 memory OK。

Start Screen开始屏幕

2 min later Screen 2 分钟后屏幕

Project: GitHub项目: GitHub

Not sure how不知道如何

this.Title = data.dt.Rows[0].ItemArray[0].ToString();

relates to涉及到

DataGrid1.Items.Refresh();

This are two different operations targeting two different target objects.这是针对两个不同目标对象的两个不同操作。

The following simply assigns the cell value to the Window.Title property:下面简单地将单元格值分配给Window.Title属性:

this.Title = data.dt.Rows[0].ItemArray[0].ToString();

Whereas然而

DataGrid1.Items.Refresh();

instructs the DataView to recreate the view.指示DataView重新创建视图。 This means cached data is discarded, but still resides in the memory.这意味着缓存数据被丢弃,但仍驻留在 memory 中。 It takes time until the garbage collector will collect this elegible objects.垃圾收集器收集这些可辨认的对象需要时间。
This is not a memory leak.这不是 memory 泄漏。
There's some serious business going on under the hood of CollectionView.Refresh .CollectionView.Refresh的背后有一些严肃的事情正在发生。
Just because it is a one liner doesn't mean you can compare its metrics to any random code line.仅仅因为它是单行代码并不意味着您可以将其指标与任何随机代码行进行比较。

If the memory usage is an issue for you, consider to use a ObservableCollection instead of the DataTable .如果 memory 用法对您来说是个问题,请考虑使用ObservableCollection而不是DataTable

If your depicted scenario is a real scenario, then using a ObservableCollection is more convenient than using a DataTable that has a single row and a single column.如果您描述的场景是真实场景,那么使用ObservableCollection比使用具有单行单列的DataTable更方便。 In your case, you would add the timestamp directly to the ObservableCollection .在您的情况下,您可以将时间戳直接添加到ObservableCollection

Some suggestions to improve your code:一些改进代码的建议:

  1. Don't use Thread and Thread.Sleep .不要使用ThreadThread.Sleep Always use the modern Task.Run and await Task.Delay instead始终使用现代Task.Runawait Task.Delay代替
  2. Avoid while(true) constructs避免while(true)构造
  3. Use a timer for periodic operations使用计时器进行定期操作
  4. Don't define public fields.不要定义公共字段。 Use properties instead.改为使用属性。 In your case the property should be read-only.在您的情况下,该属性应该是只读的。
  5. Prefer the delegate EventHandler over EventHandler<EventArgs> :更喜欢委托EventHandler而不是EventHandler<EventArgs>
event EventHandler MyEvent;

over超过

event EventHandler<EventArgs> MyEvent;

The improved implementation looks like this:改进后的实现如下所示:

DataWorker.cs数据工作者.cs

internal class DataWorker
{
  private DispatcherTimer Timer { get; }
  public DataTable Dt { get; } = new DataTable();
  private DataColumn Column { get; } = new DataColumn();
  public event EventHandler DataChanged;

  public DataWorker()
  {
    this.Column.DataType = Type.GetType("System.UInt64");
    this.Column.ColumnName = "DATA";
    this.Dt.Columns.Add(this.Column);

    this.Timer = new DispatcherTimer(
      TimeSpan.FromSeconds(1), 
      DispatcherPriority.Normal, 
      OnTimerElapsed, 
      Application.Current.Dispatcher);
  }

  private void OnTimerElapsed(object? sender, EventArgs e)
  {
    this.Dt.Clear();
    this.Dt.Rows.Add(new object[] { DateTime.Now.Ticks });
    this.DataChanged?.Invoke(this, EventArgs.Empty);
  }
}

MainWindow.xaml.cs主窗口.xaml.cs

public MainWindow(TestViewModel dataContext)
{
  var data = new DataWorker();
  DataGrid.ItemsSource = data.dt.DefaultView;
  data.DataChanged += OnDataChanged;
}

private void OnDataChanged(object obj, EventArgs e)
{
  DataGrid.Items.Refresh();
}

How to identify a memory leak如何识别 memory 泄漏

It is not enough to watch the total memory allocation.光看总的 memory 分配是不够的。 There are many reasons why the memory allocation increases over time. memory 分配随时间增加的原因有很多。 What characterizes a memory leak is that the momory contains objects that are never collected, because they are always reachable. memory 泄漏的特点是内存中包含永远不会收集的对象,因为它们总是可以访问的。 The reasons for this a various.造成这种情况的原因多种多样。 It can be because of static references or object lifetime management of event sources, data binding etc.这可能是因为 static 引用或 object 事件源的生命周期管理、数据绑定等。

To identify the leak, you must inspect the objects on the heap.要识别泄漏,您必须检查堆上的对象。 You do this by comparing snapshots of the memory.您可以通过比较 memory 的快照来做到这一点。 And by forcing garbage collection like this:并通过像这样强制垃圾收集:

for (int i = 0; i < 4; i++)
{
    GC.Collect(2, GCCollectionMode.Forced, true);
    GC.WaitForPendingFinalizers();
}

If objects are still on the heap, although you expect them to be collected because you have removed all references, you probably have a leak.如果对象仍在堆上,尽管您希望它们被收集,因为您已删除所有引用,但您可能有泄漏。

Each good profiler allows you to expand the reference tree in order to identify the class that causes the object to be reachable.每个好的分析器都允许您扩展参考树,以识别导致 object 可访问的 class。 You the have inspect this class to find the cause for the strong reference.您已检查此 class 以查找强参考的原因。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM