0

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

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

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

目前我已经没有办法解决它了。应用程序应该 24/7 全天候工作并且它必须是稳定的。

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

4

2 回答 2

0

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

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

于 2013-06-21T10:36:03.067 回答
0

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

具体结果: 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"

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

于 2013-06-19T16:11:25.897 回答