c# - WPF 应用程序中的内存分配问题(可能泄漏),使用 Pages 和 TabContol
问题描述
免责声明:
在我的应用程序中,我的内存管理存在很大问题。它们于 2018 年 5 月首次出现,但感谢 MSDN 和这里的各种文章,所以,我能够减少问题,但并没有真正解决它。在我做了一些阅读、测试和编码之后,我来到这里,因为我需要帮助。
申请背景:
我的应用是面向小客户的销售管理应用。它在远程桌面上运行,内存非常有限,因此我需要保持较低的内存使用率。MainWindow,由右侧的列表菜单组成(老式),单击菜单项后,新页面作为RadTabItem被打开(ib4 你用“Ask teleriks for that”打我,没关系,我有与 TabItems 内容相同的问题,这是一个代码示例:
private void KlientDzwoniTab(object sender, MouseButtonEventArgs e)
{
App.StronaGlowna.Cursor = Cursors.Wait;
try
{
if (CheckIfTabIsOpened("Klient dzwoni") == false)
{
_nazwakarty = "Klient dzwoni";
var strona = new KlientDzwoni();
AddItem(strona, _nazwakarty);
App.StronaGlowna.Cursor = Cursors.Arrow;
}
else
{
SelectTab("Klient dzwoni");
App.StronaGlowna.Cursor = Cursors.Arrow;
}
}
catch (Exception exception)
... catch block and stuff
和必不可少的东西,即 AddItem() 函数:
public void AddItem(Page strona, string header)
{
try
{
#region Close button region
var grid = new Grid();
var c1 = new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) };
var c2 = new ColumnDefinition { Width = new GridLength(21) };
grid.ColumnDefinitions.Add(c1);
grid.ColumnDefinitions.Add(c2);
var button = new RadButton
{
Padding = new Thickness(0, 0, 0, 0),
Margin = new Thickness(5, 0, 0, 0),
Width = 16,
Height = 16,
HorizontalAlignment = HorizontalAlignment.Stretch,
VerticalAlignment = VerticalAlignment.Top,
VerticalContentAlignment = VerticalAlignment.Top,
Content = "x",
FontWeight = FontWeights.Bold,
Background = Brushes.Transparent,
BorderThickness = new Thickness(0, 0, 0, 0),
Tag = header
};
button.Click += Zamknijtab;
var stringHeader = header.Replace(" ", "");
stringHeader = stringHeader.Replace(":", "");
stringHeader = stringHeader.Replace("/", "");
stringHeader = stringHeader.Replace("-", "");
stringHeader = stringHeader.Replace(".", "");
stringHeader = stringHeader.Replace("_", "");
var textBlock = new TextBlock
{
Text = header
};
// panel.Name = header;
grid.Children.Add(textBlock);
Grid.SetColumn(textBlock, 0);
grid.Children.Add(button);
Grid.SetColumn(button, 1);
#endregion
var content = strona.Content;
var itemToAdd = new RadTabItem
{
Header = grid,
Content = content
};
var klientDzwoni = strona as KlientDzwoni;
if (klientDzwoni != null)
{
itemToAdd.KeyDown += klientDzwoni.F3KeyDown;
}
itemToAdd.SetValue(NameProperty, stringHeader);
tabControl.Items.Add(itemToAdd);
tabControl.SelectedItem = itemToAdd;
}
catch (Exception e)
{
...catch stuff
}
}
现在,用户显然可以关闭这些选项卡,点击 Zamknijtab 事件:
public void Zamknijtab(object sender, RoutedEventArgs e)
{
try
{
var listaTabow = tabControl.Items;
var button = sender as RadButton;
if (button != null)
{
if (button.Tag != null)
{
var stringHeader = button.Tag as string;
stringHeader = stringHeader.Replace(" ", "");
stringHeader = stringHeader.Replace(":", "");
stringHeader = stringHeader.Replace("/", "");
stringHeader = stringHeader.Replace("-", "");
stringHeader = stringHeader.Replace(".", "");
stringHeader = stringHeader.Replace("_", "");
var tabs = listaTabow.Cast<RadTabItem>();
tab = tabs.Reverse().FirstOrDefault(f => f.Name == stringHeader);
if (tab == null)
{
tab = tabs.Reverse().FirstOrDefault(f => f.Name == stringHeader);
}
if (tab != null)
{
if (button.Tag.ToString().Contains("Zam:") &&
!button.Tag.ToString().Contains("podsumowanie"))
{
NumerZamowienia = stringHeader.Replace("Zam", "");
if (App.ZamowieniaCommitGet(NumerZamowienia))
{
var textBox = new Label
{
Content =
"Na zamówieniu są wprowadzone pozycje, czy chcesz usunąć zamówienie?!",
FontWeight = FontWeights.Bold,
Foreground = Brushes.Red
};
RadWindow.Confirm(new DialogParameters
{
Header = "Potwierdź zamknięcie okna",
Content = textBox,
Closed = OnConfirmClosed,
Owner = App.StronaGlowna,
OkButtonContent = "Tak",
CancelButtonContent = "Nie"
});
}
else
{
if (NumerZamowienia != null)
{
var zamowienie = Db.dst_Orders.FirstOrDefault(f => f.Numer == NumerZamowienia);
if (zamowienie != null)
{
ZamowienieId = zamowienie.Id_Order;
}
var pozycje = Db.dst_OrderLines.Where(f =>
f.Order_Id == ZamowienieId && f.Ilosc != null && f.Ilosc > 0);
if (!pozycje.Any())
{
var id = App.GetUserId();
Db.No_Order(ZamowienieId, null, id);
}
tabControl.Items.Remove(tab);
}
}
}
else
{
tabControl.Items.Remove(tab);
}
}
}
else
{
var typ = tabControl.SelectedItem as RadTabItem;
var tabs = listaTabow.Cast<RadTabItem>();
if (typ != null)
{
tab = tabs.Reverse().FirstOrDefault(f => f.Name == typ.Name);
if (tab != null)
{
tabControl.Items.Remove(tab);
}
}
}
}
else
{
var typ = tabControl.SelectedItem as RadTabItem;
var tabs = listaTabow.Cast<RadTabItem>();
if (typ != null)
{
tab = tabs.Reverse().FirstOrDefault(f => f.Name == typ.Name);
if (tab != null)
{
tabControl.Items.Remove(tab);
}
}
}
}
catch (Exception exception)
{
... Another catch
这个,本来可以稍微重构一下,但是,它就是这样。
问题:
关闭选项卡,不会释放分配给它们的内存(就像从不)。
我在 RadTabItems 中打开的所有项目都是自定义页面(使用UserControl尝试过,同样的问题)并且所有项目都存在问题。KlientDzwoni()
类是一个小类,提高内存使用量,大约 4 MB,但我有更大的页面,将它提高 100-200 MB(报告工具)。
说到内存和 MB,您可能会问,我是如何分析这些的,所以我从任务管理器开始,它显示,每次打开新标签时内存使用量都会增加,而且它永远不会下降。在阅读了这篇精彩的帖子后,我下载了 JetBrains dotMemory,它帮助我检测了我的应用程序中的内存泄漏,但它实际上并没有考虑不释放内存作为泄漏。
在我看来,这些页面(如KlientDzwoni
)由于某种原因被保存在内存中的某个位置,并且永远不会被释放并且不被认为是不需要的(因此,不会被检测为泄漏)。这是我尝试解决的问题。
我悲惨的尝试:
- 参考之前提到的文章,我尝试使用
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
. 它实际上有一点帮助(减少泄漏,约 10-20%),但并不能完全解决问题。另外,由于不建议强制 GC 工作,我有点害怕。 - BindingOperations.ClearBinding - 我觉得这个想法很酷。我的意思是,我没有在我的页面中取消绑定事件和属性,首先,因为我没有正确的事件(嗯,没有 Closing 事件,另一方面有 Unloaded),其次,它是 C# ,所以我希望 Dispatcher 和 GC 为我完成这项工作。也许我对此完全错误,我应该取消订阅所有事件并取消某些事件中的所有集合和变量,但是,当 TabItems 被删除时,它的内容(Page.Content)被删除,因此,不应该是任何事件,取消订阅,对吧?
- 使变量和属性无效,例如 TabItem.Content、变量、保存页面对象等。 0 结果。
- 使用UserControl,我在上面提到过,它根本没有帮助。
- 放弃我心爱的框架 Telerik(它不是),并使用 TabItems,而不是 RadTabItems,结果为 0。
实际上没有帮助的是:
有很多关于内存泄漏(我仍然不确定是否是泄漏)和 TabControl 问题的帖子、文章、论文,但是,我相信它们的构造很差,因此由于以下几个原因,答案不准确:
- 许多答案都集中在“内存泄漏”上,包括如何检查它是否是内存泄漏,或者不是,或者它是,我不知道,Apricot。它问题的核心是,内存正在被分配并且永远不会被释放。我不在乎我们如何称呼这个问题。
- 其他答案将问题/帖子/文章/答案与相同的反复出现的问题联系起来。此链接使用最频繁。
我无法找到此问题的实际解决方案。在过去的几年里,它已经多次发布在 SO、MSDN 和其他地方,但我从来没有找到像“谢谢大家,问题是 xxx,修复是 yyy”这样的 OP 答案。但是有某种模式 - 有人带有 TabItem 内存问题,并且永远不会留下正确的答案。
那么,最后有人可以回答“如何释放分配的内存”这个问题并结束这个无休止的问题吗?
编辑2:
在此处上传示例项目:
https://1drv.ms/u/s!Ali8Cn1kitEDhEwn-WAEEl04talS 它由 Telerik 组件组成,但可以替换为 vanilla WPF,以呈现相同的行为。
解决方案
在MSDN上发布此问题后,确实有人回答了它。所以解决方案是,从按钮取消订阅事件,清除选项卡项并调用 GC,这是解决我的问题的代码:
public void Zamknijtab(object sender, RoutedEventArgs e)
{
var listaTabow = tabControl.Items;
var button = sender as RadButton;
if (button != null)
{
button.Click -= Zamknijtab;
var typ = tabControl.SelectedItem as RadTabItem;
var tabs = listaTabow.Cast<RadTabItem>();
if (typ != null)
{
tab = tabs.Reverse().FirstOrDefault(f => f.Name == typ.Name);
if (tab != null)
{
tabControl.Items.Remove(tab);
tab = null;
}
}
GC.Collect();
}
else
{
var typ = tabControl.SelectedItem as RadTabItem;
var tabs = listaTabow.Cast<RadTabItem>();
if (typ != null)
{
tab = tabs.Reverse().FirstOrDefault(f => f.Name == typ.Name);
if (tab != null)
{
tabControl.Items.Remove(tab);
}
}
}
App.main.Cursor = Cursors.Arrow;
}
推荐阅读
- haskell - Haskell:元组的递归定义
- javascript - jquery ajax post和axios post的区别
- jquery - Jquery默认下拉值并将更改保存在数据表中以供下载
- node.js - JSbarcode不呈现
- python - 使用 Pandas 在 excel 中突出显示两列中的重复值
- css - CSS Fixed Header 仅显示 2 行
- arduino - 无法从 Lora 收音机接收 radiopacaket
- javascript - 特征中的读取值始终为空,但通知有效吗?
- flutter - 在 flutter_local_notifications 上禁用振动
- listview - Flutter Listview.builder, RangeError Index Not in range 0..1, inclusive: 2. 我该如何解决?