Window 对象在关闭后未释放,即使在 GC Collect 和 WaitForPendingFinalizers 之后?

Window object not released after being closed, even after GC Collect and WaitForPendingFinalizers?

这是一个简单的测试应用程序,可帮助了解 WPF 内存使用情况。我想了解的关键是为什么 MainWindow 仍然被引用并且它的内存没有释放,即使在关闭并等待 GC 完成后也是如此?


(见下面的代码清单)

文本“MainWindow finalizer”在快照 #2 时并未执行,这似乎出乎意料。为了进行调查,我使用 VS 诊断工具在代码清单中指示的点拍摄了两个内存快照。

下面是两张快照的VS对比:

这表明 MainWindow 仍然存在。但是,如果没有任何内容引用它,为什么呢?向下钻取(再次使用诊断工具)发现毕竟有一个参考:

还有其他对象也引用了 MainWindow,但它们最终都形成了一个循环回到它,所以我不认为它们是真正使引用保持活动状态的“根”对象。但对于 MediaContext / Dispatcher 二人组来说,情况并非如此。

据我所知,Dispatcher 是每个线程一次 运行,因此它本身似乎没问题。但是它拥有的 MediaContext 怎么了,它又持有我的 MainWindow?

这正常吗?是“内存泄漏”吗?为什么会这样?

此外,重要的是我如何/我应该如何真正摆脱 MainWindow 对象?


App.xaml:

<Application
    x:Class="memtest.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:memtest"
    StartupUri="MainWindow.xaml"
    Startup="Application_Startup"
    >
    <Application.Resources/>
</Application>

App.xaml.cs:

namespace memtest
{
    public partial class App : Application
    {
        private void Application_Startup(object sender, StartupEventArgs e)
        {
            // *** SNAPSHOT 1 ***

            ShutdownMode = System.Windows.ShutdownMode.OnExplicitShutdown;

            MainWindow window = new MainWindow();
            window.Show();
            window.Close();
            window = null;

            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();

            // *** SNAPSHOT 2 ***
        }
    }
}

MainWindow.xaml.cs:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        Debug.WriteLine("MainWindow constructor");
    }

    ~MainWindow()
    {
        // Never reached
        Debug.WriteLine("MainWindow finalizer");
    }
}

MainWindow.XAML是VS默认创建的,里面只包含一个空的grid。

项目中没有其他代码。

这是一个 .NET 4.72 项目。


这不完全是 A WPF window doesn't release the memory after closed 的骗局,因为它没有使用 WaitForPendingFinalizers() 也没有使用显式终结器。这个问题没有有效答案。

这个测试有几个错误,也解释了here

  • 您正在拍摄第二张快照,而 MainWindow 变量仍在堆栈帧上。允许 JIT 优化您对 window = null; 的分配,因为它可以清楚地看到该变量之后不再使用。此外,堆栈帧的 GC 报告并不准确(相对于您的来源),堆栈上可能存在隐藏的副本。将测试代码移动到您 return 所在的单独方法中,以确保没有对 MainWindow 的引用留在堆栈中。 (在修复下一点后技术上没有必要,但我提到它是为了完整性,以便人们在编写 GC 测试时理解这一点。)
  • 您没有给多线程 WPF 渲染引擎清理的时间,关闭和强制 GC 不足以与渲染引擎同步以清理其资源
  • 您要将 StartupUri="MainWindow.xaml" 留在应用程序中,请将其删除以简化使用固定代码的测试

执行测试的正确方法是启动一个 DispatcherTimer 并在那里拍摄第二张快照,对我来说,MainWindow 已经消失了。

private void Application_Startup(object sender, StartupEventArgs e)
{
    // *** SNAPSHOT 1 ***

    ShutdownMode = System.Windows.ShutdownMode.OnExplicitShutdown;

    RunTest();

    GC.Collect();
    GC.WaitForPendingFinalizers();
    GC.Collect();

    new DispatcherTimer(TimeSpan.FromSeconds(1), DispatcherPriority.Normal, Callback, Dispatcher).Start();
}

private void Callback(object sender, EventArgs e)
{
    // *** SNAPSHOT 2 ***
}

private static void RunTest()
{
    MainWindow window = new MainWindow();
    window.Show();
    window.Close();
    window = null;
}