【问题标题】:Memory leaks while refreshing WPF Toolkit Line Chart?刷新 WPF 工具包折线图时内存泄漏?
【发布时间】:2013-06-19 14:06:42
【问题描述】:

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

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

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

然后,我启动一个 System.Timers.Timer 来刷新应用程序流程的图表。这里是负责刷新图表的代码sn-p:

    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 以编程方式添加。

问题是,几分钟后,这段代码使用的内存增长得非常快(有十张这样的图表,15 分钟内大约 400 MB)。 正如您在 cmets 中看到的那样,由 SO 上的问题和答案引导,我尝试做几件事来防止我的应用程序的 RAM 使用量增加,我还尝试调整整个算法,但没有成功。

目前我没有办法解决它。应用程序应该 24/7 全天候运行,并且必须稳定。

经过几天几夜寻找解决方案,如果你能帮助我解决这个问题,我会很高兴。

【问题讨论】:

    标签: c# memory-leaks wpftoolkit


    【解决方案1】:

    看来你是对的。
    我写了一个简单的项目,模仿你的情况,你的结果被复制了。
    即使数据量看起来很合理,内存消耗也是巨大的。

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

    我发布我的代码以防你想使用参数:

    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"

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

    【讨论】:

    • 感谢您的快速回复。我尝试使用您的代码的参数,但仍然存在大量数据消耗。有趣的是,我尝试使用 D3 DynamicDataDisplay 图表,效果还是一样。而且,当我修改我的代码以在每个 timer_elapsed 方法中调用 chart.Series.Clear() 时,结果也是相同的 - 内存仍在增长。我开始考虑把 WinForms ZedGraph 放进去,因为我对它有美好的回忆。
    • 奇怪的是,如果间隔为 1000 毫秒,则不会重现该行为。也许如果我们设置 300ms 数据获取需要超过 300ms 并且调度程序队列开始无休止地增长......不幸的是现在没有时间检查它
    【解决方案2】:

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

    @FireAlkazar:无论如何,感谢您的回复。干杯!

    【讨论】:

      猜你喜欢
      • 2016-01-27
      • 1970-01-01
      • 1970-01-01
      • 2014-10-27
      • 1970-01-01
      • 2016-04-02
      • 1970-01-01
      • 2011-02-21
      相关资源
      最近更新 更多