WPF窗口不会释放其资源,直到程序终止



我一直在阅读关于WPF内存处理的文章,并跟踪了前5名和前8名的内存泄漏陷阱,但在我目前的情况下,没有任何帮助。

我的软件出现了一个问题,WPF在程序终止之前不会释放它的内存。如果我让它永远消失,无论我做什么,它都会导致OutOfMemoryException。我已经设法在一个小样本中隔离了这个问题,以显示它是如何不释放内存的,即使我不再使用它。以下是我如何重现问题:

我创建了两个项目,一个控制台程序和一个WPF应用程序。在我的WPF应用程序中,我有一个MainWindow.xaml,里面什么都没有:

<Window x:Class="MemoryLeakWpfApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:MemoryLeakWpfApp"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800" Loaded="MainWindow_OnLoaded">
<Grid>
</Grid>
</Window>

我确实订阅了Loaded事件,我用它来立即关闭窗口,可以在这里的.cs文件中看到:

public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Debug.WriteLine("Constructing",GetType().Name);
}
~MainWindow()
{
Debug.WriteLine("Deconstructing", GetType().Name);
}
private void MainWindow_OnLoaded(object sender, RoutedEventArgs e)
{
Close();
}
}

我还在构造函数和解构器中添加了调试行,这样我就可以跟踪它是何时创建和丢弃的。然后,我在WPF应用程序中创建了一个Controller类,它表示这个WPF类库的入口点,该类库有一个创建和显示窗口的方法:

public class Controller
{
public void Execute()
{
MainWindow window = new MainWindow();
window.ShowDialog();
Debug.WriteLine("Constructing", GetType().Name);
}
~Controller()
{
Debug.WriteLine("Deconstructing", GetType().Name);
}
}

在这里,我还添加了调试跟踪行。我没有App.xaml,因为这个WPF项目在其属性中被设置为类库。这就是WPF部分。在控制台项目中,我将以下代码添加到我的主类中:

[STAThread]
static void Main(string[] args)
{
for (int i = 0; i < 100; i++)
{
Controller controller = new Controller();
Console.WriteLine("Test " + i);
controller.Execute();
}
Console.WriteLine("Pressing enter will close this");
Console.ReadLine();
Debug.WriteLine("Party is over, lets leave");
}

所以基本上,设置是我有一个控制台类,它想要显示一个对话框。它为WPF应用程序创建控制器并调用Execute。控制器显示加载完成后立即关闭的窗口。控制台类然后创建一个新的控制器来重新执行该过程。现在,这就是我在输出中看到的:

MainWindow: Constructing
Controller: Constructing
MainWindow: Constructing
Controller: Constructing
Controller: Deconstructing
MainWindow: Constructing
Controller: Constructing
Controller: Deconstructing
MainWindow: Constructing
Controller: Constructing
Controller: Deconstructing
MainWindow: Constructing
Controller: Constructing
Controller: Deconstructing

控制器正在构建和解构,但窗口不是。然而,当for循环完成,我按下回车键让程序运行完时,我得到的是:

Party is over, lets leave
MainWindow: Deconstructing
Controller: Deconstructing
MainWindow: Deconstructing
Controller: Deconstructing
MainWindow: Deconstructing
MainWindow: Deconstructing
MainWindow: Deconstructing
MainWindow: Deconstructing
MainWindow: Deconstructing
MainWindow: Deconstructing
MainWindow: Deconstructing
MainWindow: Deconstructing
MainWindow: Deconstructing
MainWindow: Deconstructing
MainWindow: Deconstructing

突然间,MainWindow的所有实例都在解构,但只有当程序用完时,而不是当我们在for循环中丢弃引用时。这意味着在我们的程序中,在发生OutOfMemoryException之前,我们只能打开窗口的次数有限。

但这个价值150万美元的问题是:我如何说服WPF在程序运行时释放内存,而不是在程序关闭时释放内存

您声称自己是[STAThread],但没有消息泵。如果没有消息泵,你就不是真正的STA。在这种特殊情况下,这意味着WPF永远没有机会清理其资源。WPF可能正在向消息队列发布消息,但这些消息从未被拾取。

由于WPF是一个多线程系统,它必须执行后台操作,包括在多个线程之间进行同步。为了回到主线程,它使用了Dispatcher基础结构,而您没有正确设置该基础结构。

要解决问题,您需要在STA线程上运行WPFDispatcher,而不是实现自己的循环。

此外,为了完整起见,请链接到一篇将我带到这里的相关帖子。确保在设置了调度程序基础结构之后测量的内容是正确的。

因此,根据Peter Duniho在问题中的评论,我开始测试重用窗口的WindowService是否有用,它确实有用。以下是我在示例项目中创建的非常粗糙的服务:

public class ViewFactory
{
private static ViewFactory _instance;
private MainWindow mainWindow = null;
private ViewFactory()
{
Debug.WriteLine("ViewFactory created");
mainWindow = new MainWindow();
}
public static ViewFactory Instance
{
get
{
if (_instance == null)
{
_instance = new ViewFactory();
}
return _instance;
}
}
public MainWindow GetMainWindow()
{
return mainWindow;
}
}

现在有了这个系统,我需要调整我的视图,因为我不能在任何时候关闭我的窗口,因为这会释放一些资源,因此我将无法重用到窗口。在视图中,我必须订阅关闭事件:

<Window x:Class="MemoryLeakWpfApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:MemoryLeakWpfApp"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800" Loaded="MainWindow_OnLoaded" Closing="MainWindow_OnClosing">
<Grid>
</Grid>
</Window>

在代码隐藏文件中,处理程序如下所示:

private void MainWindow_OnClosing(object sender, System.ComponentModel.CancelEventArgs e)
{
e.Cancel = true;
Visibility = Visibility.Hidden;
}

此处理程序停止任何关闭窗口的尝试并将其隐藏。当调用ShowDialog时,它将再次显示它。我已经在我的软件上测试了几个小时,内存是稳定的。

最新更新