简体   繁体   English

刷新WPF工具包折线图时发生内存泄漏?

[英]Memory leaks while refreshing WPF Toolkit Line Chart?

I am writing application, which will monitor multiple computers, store data in database and display it on dashboard with multiple charts refreshing every couple of seconds. 我正在编写应用程序,它将监视多台计算机,将数据存储在数据库中,并将其显示在仪表板上,并且每两秒钟刷新一次多个图表。

Here is my xaml source for creating chart on wpf UserControl: 这是我在wpf UserControl上创建图表的xaml来源:

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

Then, I start a System.Timers.Timer to refresh chart on application flow. 然后,我启动System.Timers.Timer以刷新应用程序流上的图表。 Here is code snippet responsible for refreshing the chart: 这是负责刷新图表的代码段:

    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);
}

} }

I don't know number of hosts connected to my app at startup, hence LineSeries added programmatically. 我不知道启动时连接到我的应用程序的主机数量,因此LineSeries以编程方式添加。

Problem is, that after couple of minutes memory used by this code is growing very fast (with ten charts like this one, about 400 MB in 15 minutes). 问题是,此代码使用了几分钟后,内存增长非常快(有十张这样的图表,在15分钟内大约有400 MB)。 As You can see in comments, led by questions and answers found on SO, I tried to do several things to prevent my application's RAM usage from growing, I also tried to tune up whole algorythm, but with no success. 正如您在评论中看到的那样,以SO上的问题和答案为主导,我试图做一些事情来防止应用程序的RAM使用量增长,我也试图调整整个算法,但没有成功。

Currently I'm running out of ideas how to fix it. 目前,我没有足够的想法来解决它。 Application is supposed to work 24/7 and it has to be stable. 应用程序应该可以24/7运行,并且必须稳定。

After some days and nights searching for solution, I will be very happy, if You could help me with this issue. 经过几天几夜的寻找解决方案之后,如果您可以帮助解决此问题,我将非常高兴。

Seems like you are right. 好像你是对的。
I wrote simple project, emulating you case and your results are repoduced. 我写了一个简单的项目,模拟了您的情况,并重复了您的结果。
Even if amoumt of data seems to be quete reasonable, memory consumption is huge. 即使数据量似乎合理,内存消耗也是巨大的。

Concrete results: LinePointsCount = 128, LinesCount = 10, TimerIntervalInMilliseconds = 300 - not limited memory consumption LinePointsCount = 128, LinesCount = 10, TimerIntervalInMilliseconds = 1000 - memory doesnt increase 140Mb 具体结果:LinePointsCount = 128,LinesCount = 10,TimerIntervalInMilliseconds = 300-不限制内存消耗LinePointsCount = 128,LinesCount = 10,TimerIntervalInMilliseconds = 1000-内存不增加140Mb

I publish my code in case you want to play with params: 如果您想使用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; }
}

I used WPFToolkit.DataVisualization version="3.5.50211.1" 我使用了WPFToolkit.DataVisualization version =“ 3.5.50211.1”

And by the way, maybe you also need to remove Lines from the chart as you remove them from data structure. 顺便说一句,当您从数据结构中删除线时,也许还需要从图表中删除线。
So anyway problem exists and probable solution is to reduce an amount of data you provide for charts and to increase update interval. 因此无论如何存在问题,可能的解决方案是减少为图表提供的数据量并增加更新间隔。

After some additional modifications, I've decided to create my own charting control. 经过一些其他修改后,我决定创建自己的图表控件。 Now, I draw line chart myself on a canvas and memory consumption is stable. 现在,我自己在画布上绘制折线图,​​并且内存消耗稳定。 It's also much faster, I must add ;]. 它也要快得多,我必须添加;]。

@FireAlkazar: Anyway, thanks for replies. @FireAlkazar:无论如何,感谢您的答复。 Cheers! 干杯!

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

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