刷新WPF工具箱折线图时内存泄漏
本文关键字:内存 泄漏 折线图 WPF 工具箱 刷新 | 更新日期: 2023-09-27 18:19:14
我正在编写应用程序,它将监视多台计算机,将数据存储在数据库中并显示在仪表板上,每隔几秒钟刷新多个图表。
这是我在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/7,它必须是稳定的。
经过几天几夜的寻找,我将非常高兴,如果你能帮助我解决这个问题。
看来你是对的。
我写了一个简单的项目,模拟了你的案例,并复制了你的结果。即使数据量看起来相当合理,内存消耗也是巨大的。
具体的结果:LinePointsCount = 128, LinesCount = 10, TimerIntervalInMilliseconds = 300 -不限制内存消耗LinePointsCount = 128, LinesCount = 10, TimerIntervalInMilliseconds = 1000 -内存不增加140Mb
我发布了我的代码,以防你想玩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; }
}
我使用WPFToolkit。DataVisualization version = " 3.5.50211.1 "
顺便说一下,也许你还需要从图表中删除线条,因为你从数据结构中删除它们。所以无论如何,问题是存在的,可能的解决方案是减少你为图表提供的数据量,并增加更新间隔。
经过一些额外的修改后,我决定创建我自己的图表控件。现在,我自己在画布上绘制折线图,内存消耗是稳定的。我必须说,它也快得多;]。
@FireAlkazar:不管怎样,谢谢你的回复。干杯!