【问题标题】:GC.WaitForPendingFinalizers not working in async method?GC.WaitForPendingFinalizers 不在异步方法中工作?
【发布时间】:2014-02-21 15:38:19
【问题描述】:

我有一些单元测试来验证使用 Wea​​kReferences 的对象是否正常工作。在将这些对象重构为异步工作后,单元测试会失败。这似乎是由于使用异步时 GC.WaitForPendingFinalizers 不工作(或工作方式不同?)引起的。

为了检查这一点,我创建了一个带有两个按钮的简单 WPF 应用程序,一个带有常规 Click 事件,一个带有异步点击事件。

当我按下 NormalGCTest 按钮时,会显示“收集的对象垃圾:True”。

但是当我按下 AsyncGCTest 按钮时,会显示“收集的对象垃圾:False”。

发生了什么事?有没有办法在我的测试中强制进行完整的垃圾回收?

    private void NormalGCTest(object sender, RoutedEventArgs e)
    {
        var temp1 = new object();
        var temp2 = new WeakReference(temp1);
        temp1 = null;
        GC.Collect();
        GC.WaitForPendingFinalizers();
        temp1 = temp2.Target;
        System.Diagnostics.Debug.WriteLine("object garbage collected: {0}", temp1 == null);
    }

    private async void AsyncGCTest(object sender, RoutedEventArgs e)
    {
        var temp1 = new object();
        var temp2 = new WeakReference(temp1);
        temp1 = null;
        GC.Collect();
        GC.WaitForPendingFinalizers();
        temp1 = temp2.Target;
        System.Diagnostics.Debug.WriteLine("object garbage collected: {0}", temp1 == null);
        await Task.Delay(0);
    }

【问题讨论】:

  • 可能由于某种原因,生成的闭包类仍然引用了 temp1。我不知道 async/await 可能保证的任何 GC 属性。 IOW 无法保证 C# 语言在调用 Collect 时 temp1 已死。
  • 不建议显式调用 GC。你确定你需要它?顺便说一句,AFAIK GC 适用于所有线程(清理时停止所有线程)。结合 async 使用感觉有点奇怪。
  • 在控制台应用程序中,两者都对我有用...您是否在调试器下运行?这很可能会有所作为。
  • 在本地运行它,我得到TrueTrue - 它被双向收集
  • @PetarRepac 这是一个涉及弱引用的单元测试,所以当然需要调用GC。我不认为它是在实际代码中调用的。

标签: c# wpf garbage-collection async-await


【解决方案1】:

对我来说,它已经很好用了 - 我得到了 True / True。但是,您可以尝试一些事情来澄清正在发生的事情 - 例如:

var wr = CreateWeakReference();
Console.WriteLine("object available: {0}", wr.Target != null);
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
GC.WaitForPendingFinalizers();
Console.WriteLine("object garbage collected: {0}", wr.Target == null);

static WeakReference CreateWeakReference()
{
    return new WeakReference(new object());
}

这使用较少的局部变量,这可能会导致混淆,具体取决于编译器如何翻译 C#。它还使用了更激进的GC.Collect

但重要的是:停止调用垃圾收集器。你几乎不应该这样做。最后的想法 - 你可能想避免async void。是的,我知道这是一个事件处理程序,但一个不错的技巧是立即调用非async void 方法(如有必要,使用await)。

【讨论】:

  • 关于你的最后一句话:我非常清楚这些缺点。我只在单元测试中使用 GC.Collect() 并且使用 async void 来简化示例。
  • 将对象创建放在静态方法中解决了问题(也在我的单元测试中)。
  • @MarcelW 那么它几乎肯定与编译器在将方法转换为状态机(局部变量成为字段)时如何选择处理局部变量(包括编译器引入的瞬态局部变量)有关。因此,不同的编译器(或不同的编译器选项)可能会产生不同的结果。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-03-13
  • 1970-01-01
  • 2017-04-17
  • 1970-01-01
  • 2016-03-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多