在程序终止之前,WPF窗口不会释放其资源

时间:2020-03-15 11:33:17

标签: wpf memory-leaks

我一直在阅读WPF的内存处理方法,并一直跟踪内存泄漏的前5名和前8名,但在当前情况下没有任何帮助。

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

我创建了2个项目,一个控制台程序和一个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循环完成并且按Enter键以使程序用完时,我得到了:

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发生之前打开窗口。

但是百万美元半的问题是:我如何说服WPF在程序运行时释放其内存,而不是在程序关闭时释放它?

2 个答案:

答案 0 :(得分:1)

因此,在彼得·杜尼奥(Peter Duniho)对问题进行评论之后,我开始测试可重用Windows的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时,它将再次显示它。我已经在软件上测试了几个小时,并且内存稳定。

答案 1 :(得分:1)

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

由于WPF是多线程系统,因此它必须执行后台操作,包括在多个线程之间进行同步。要返回主线程,它使用Dispatcher基础结构,您尚未正确设置。

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

为了完整起见,还链接到将我带到这里的related post。设置调度程序基础结构后,请确保您测量正确的事情。