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 がプログラムで追加されました。

問題は、このコードによって使用されるメモリが数分後に非常に急速に増加することです (このようなグラフが 10 個ある場合、15 分間で約 400 MB になります)。コメントでわかるように、SO で見つかった質問と回答に導かれて、アプリケーションの RAM 使用量の増加を防ぐためにいくつかのことを試みました。また、アルゴリズム全体を調整しようとしましたが、成功しませんでした。

現在、私はそれを修正する方法のアイデアが不足しています。アプリケーションは 24 時間年中無休で動作し、安定している必要があります。

何日も何日も解決策を探した後、この問題について助けていただければ幸いです。

4

2 に答える 2

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 に答える