简体   繁体   中英

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:

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

Then, I start a System.Timers.Timer to refresh chart on application flow. 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.

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). 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.

Currently I'm running out of ideas how to fix it. Application is supposed to work 24/7 and it has to be stable.

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

I publish my code in case you want to play with 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"

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. Cheers!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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