刷新WPF工具箱折线图时内存泄漏

本文关键字:内存 泄漏 折线图 WPF 工具箱 刷新 | 更新日期: 2023-09-27 18:19:14

我正在编写应用程序,它将监视多台计算机,将数据存储在数据库中并显示在仪表板上,每隔几秒钟刷新多个图表。

这是我在wpf UserControl上创建图表的xaml源代码:

<chartingToolkit:Chart x:Name="chart" BorderThickness="0" Foreground="Gray"/>

然后,我启动一个System.Timers.Timer来刷新应用程序流的图表。下面是负责刷新图表的代码片段:

    private Dictionary<string, List<RamPlot>> data = new Dictionary<string, List<RamPlot>>();
void refreshChartTimer_Elapsed(object sender, ElapsedEventArgs e)
{
    DateTime now = DateTime.Now;
    lock (lockObject)
    {
        //Getting info about hosts from my storage
        List<HostInfo> infos = LiveHostInfoManager.GetInstance().GetHostInfos();
        this.Dispatcher.Invoke(new Action(() =>
        {
            foreach (HostInfo info in infos)
            {
                //data contains info about host, so I add new values to existing one, creating data for linechart
                if (data.ContainsKey(info.HostName))
                {
                    data[info.HostName].Add(new RamPlot(DateTime.Now, (info.RamInfo.TotalSize - info.RamInfo.CurrentlyAvailable) / info.RamInfo.TotalSize));
                    //I want to display on chart only last 20 readings
                    if (data[info.HostName].Count > 20)
                    {
                        data[info.HostName].RemoveAt(0);
                    }
                }
                else
                {
                //If the host is not in my dictionary (connected before last iteration was performed), I add it to my dictionary
                    if (info.RamInfo != null)
                    {
                        List<RamPlot> plot = new List<RamPlot>();
                        //Thought, that it can be due to List's load factor, hence I set up capacity. Apparently - not.
                        plot.Capacity = 25;
                        plot.Add(new RamPlot(DateTime.Now, (info.RamInfo.TotalSize - info.RamInfo.CurrentlyAvailable) / info.RamInfo.TotalSize));
                        data.Add(info.HostName, plot);
                    }
                }
            }
            //for hosts that are no longer available, I perform cleanup to get rid of them from my linechart
            List<string> keysToDelete = new List<string>();
            foreach (KeyValuePair<string, List<RamPlot>> kvp in data)
            {
                bool exists = false;
                foreach (HostInfo info in infos)
                {
                    if (info.HostName.Equals(kvp.Key))
                    {
                        exists = true;
                        break;
                    }
                }
                if (!exists)
                {
                    keysToDelete.Add(kvp.Key);
                }
            }
            foreach (string key in keysToDelete)
            {
                data.Remove(key);
            }
        //Here I attach my data to line chart. If I comment this block, I detect no memory leaks
        foreach (KeyValuePair<string, List<RamPlot>> kvp in data)
        {
            bool exists = false;
            foreach (LineSeries series in chart.Series)
            {
                if (series.Title.ToString().Equals(kvp.Key) && !string.IsNullOrEmpty(kvp.Key))
                {
                    series.ItemsSource = null;
                    series.ItemsSource = kvp.Value;
                    exists = true;
                    break;
                }
            }
            if (!exists && !string.IsNullOrEmpty(kvp.Key))
            {
                LineSeries series = new LineSeries();
                series.Title = kvp.Key;
                series.IndependentValueBinding = new Binding("Date");
                series.DependentValueBinding = new Binding("Usage");
                series.ItemsSource = kvp.Value;
                chart.Series.Add(series);
            }
        }
    }));
    //Thought that if I recreate all data structure, some garbage might be cleaned up by GC. Apparently - not.
    data = new Dictionary<string, List<RamPlot>>(data);
}

}

我不知道在启动时连接到我的应用程序的主机数量,因此以编程方式添加了LineSeries。

问题是,几分钟后,这段代码使用的内存增长非常快(有10个这样的图表,15分钟大约400 MB)。正如你在评论中看到的,由SO上的问题和答案所引导,我试图做一些事情来防止我的应用程序的RAM使用增长,我也试图调整整个算法,但没有成功。

目前我已经想不出如何修复它了。应用程序应该工作24/7,它必须是稳定的。

经过几天几夜的寻找,我将非常高兴,如果你能帮助我解决这个问题。

刷新WPF工具箱折线图时内存泄漏

看来你是对的。
我写了一个简单的项目,模拟了你的案例,并复制了你的结果。即使数据量看起来相当合理,内存消耗也是巨大的。

具体的结果:LinePointsCount = 128, LinesCount = 10, TimerIntervalInMilliseconds = 300 -不限制内存消耗LinePointsCount = 128, LinesCount = 10, TimerIntervalInMilliseconds = 1000 -内存不增加140Mb

我发布了我的代码,以防你想玩params:

public partial class MainWindow : Window
{
    const int LinePointsCount = 128;
    const int LinesCount = 20;
    const int TimerIntervalInMilliseconds = 1000;
    private static DateTime Current = DateTime.Now;
    Random _random = new Random();
    List<string> _chartNames;
    public MainWindow()
    {
        InitializeComponent();
        _chartNames = Enumerable.Repeat(1, LinesCount).Select((con, index) => index.ToString()).ToList();
    }
    Timer _timer;
    private void MainWindow_OnLoaded(object sender, RoutedEventArgs e)
    {
        _timer = new Timer((o) => Dispatcher.Invoke(new Action(ShowData)));
        _timer.Change(0, TimerIntervalInMilliseconds);
    }
    private void MenuItem_OnClick(object sender, RoutedEventArgs e)
    {
    }
    private void ShowData()
    {
        var data = GetData();
        foreach (KeyValuePair<string, List<RamPlot>> kvp in data)
        {
            bool exists = false;
            foreach (LineSeries series in chart.Series)
            {
                if (series.Title.ToString().Equals(kvp.Key) && !string.IsNullOrEmpty(kvp.Key))
                {
                    series.ItemsSource = null;
                    series.ItemsSource = kvp.Value;
                    exists = true;
                    break;
                }
            }
            if (!exists && !string.IsNullOrEmpty(kvp.Key))
            {
                LineSeries series = new LineSeries();
                series.Title = kvp.Key;
                series.IndependentValueBinding = new Binding("Date");
                series.DependentValueBinding = new Binding("Usage");
                series.ItemsSource = kvp.Value;
                chart.Series.Add(series);
            }
        }
    }
    Dictionary<string, List<RamPlot>> GetData()
    {
        var result = new Dictionary<string, List<RamPlot>>();
        var chartName = GetRandomChartName();
        result.Add(chartName, new List<RamPlot>
            {
                new RamPlot{Date = Current, Usage = 100},
                new RamPlot{Date = Current.AddDays(-LinePointsCount), Usage = 300},
            });

        var random = _random.Next(101, 300);
        for (int i = 1; i < LinePointsCount; i++)
        {
            var newElement = new RamPlot { Date = Current.AddDays(-i), Usage = random };
            result[chartName].Add(newElement);
        }
        return result;
    }
    string GetRandomChartName()
    {
        var nextIndex = _random.Next(0, _chartNames.Count);
        return _chartNames[nextIndex];
    }
}
public class RamPlot
{
    public DateTime Date { get; set; }
    public int Usage { get; set; }
}

我使用WPFToolkit。DataVisualization version = " 3.5.50211.1 "

顺便说一下,也许你还需要从图表中删除线条,因为你从数据结构中删除它们。
所以无论如何,问题是存在的,可能的解决方案是减少你为图表提供的数据量,并增加更新间隔。

经过一些额外的修改后,我决定创建我自己的图表控件。现在,我自己在画布上绘制折线图,内存消耗是稳定的。我必须说,它也快得多;]。

@FireAlkazar:不管怎样,谢谢你的回复。干杯!