WPF window 在程序终止之前不会释放其资源

WPF window wont release its resources untill program terminates

我一直在阅读有关 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>

我确实订阅了用于立即关闭 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 应用程序中创建一个控制器 class,它表示此 WPF class 库的入口点,该库具有创建和显示 window:

的方法
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 项目在其属性中设置为 Class 库。那是 WPF 部分。在控制台项目中,我将以下代码添加到我的主 class:

[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");
}

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

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

控制器正在构建和解构,但 window 不是。但是,当 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 发生之前,我们只有有限的次数可以打开 window。

但最重要的问题是:我如何说服 WPF 在程序 运行ning 而不是程序关闭时释放内存?

因此,在 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,因为这会释放一些资源,因此我将无法重用到window。在视图中我必须订阅关闭事件:

<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;
}

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

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

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

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

此外,为了完整起见,link 到 将我带到这里。确保在设置调度程序基础结构后衡量正确的事情。