垃圾收集异步方法

时间:2017-02-03 11:30:43

标签: c# async-await

我刚注意到有关垃圾收集的一些非常奇怪的事情。

WeakRef方法按预期收集对象,而async方法报告该对象仍处于活动状态,即使我们已强制进行垃圾回收。有什么想法吗?

class Program
{
    static void Main(string[] args)
    {
        WeakRef();
        WeakRefAsync().Wait();
    }

    private static void WeakRef()
    {
        var foo = new Foo();
        WeakReference fooRef = new WeakReference(foo);
        foo = null;
        GC.Collect();
        Debug.Assert(!fooRef.IsAlive);
    }

    private static async Task WeakRefAsync()
    {
        var foo = new Foo();
        WeakReference fooRef = new WeakReference(foo);
        foo = null;
        GC.Collect();
        Debug.Assert(!fooRef.IsAlive);
    }
}


public class Foo
{

}

1 个答案:

答案 0 :(得分:8)

  

WeakRef方法按预期收集对象

没有理由期待这一点。例如,在Linqpad中尝试,它不会发生在调试版本中,尽管调试和发布版本的其他有效编译都可能有这两种行为。

在编译器和抖动之间,他们可以自由地优化空赋值(毕竟没有使用foo之后),在这种情况下,GC仍然可以看到线程有一个引用对象而不是收集它。相反,如果没有foo = null的赋值,他们可以自由地意识到foo不再被使用,并重新使用持有它的内存或寄存器{{1} (或者确实是完全不同的东西)并收集fooRef

因此,既然有foo,有效的GC都可以将foo = null视为有根或无根,我们可以合理地预期这两种行为。

尽管如此,所看到的行为是合理的期望可能会发生什么,但不保证值得指出。

好的,除此之外,让我们来看看这里发生了什么。

foo方法生成的状态机是一个结构,其中的字段对应于源中的本地。

所以代码:

async

有点像:

var foo = new Foo();
WeakReference fooRef = new WeakReference(foo);
foo = null;
GC.Collect();

但是,现场访问总是在本地进行。因此,在这方面,几乎喜欢:

this.foo = new Foo();
this.fooRef = new WeakReference(foo);
this.foo = null;
GC.Collect();

并且var temp0 = new Foo(); this.foo = temp0; var temp1 = new WeakReference(foo); this.fooRef = temp1; var temp2 = null; this.foo = temp2; GC.Collect(); 未被取消,因此GC会将temp0视为root。

您的代码有两个有趣的变体:

Foo

var foo = new Foo();
WeakReference fooRef = new WeakReference(foo);
foo = null;
await Task.Delay(0);
GC.Collect();

当我运行它时(再次,在处理本地的内存/寄存器的方式上的合理差异可能导致不同的结果)第一个具有与您的示例相同的行为,因为它调用另一个var foo = new Foo(); WeakReference fooRef = new WeakReference(foo); foo = null; await Task.Delay(1); GC.Collect(); 方法和Task它,该方法返回一个已完成的任务,以便await立即移动到同一底层方法调用中的下一个事件,即await

第二个行为看到收集了GC.Collect(),因为Foo在那时返回,然后状态机的await方法在大约一毫秒之后再次调用。由于这是对幕后方法的新调用,因此没有MoveNext()的本地引用,因此GC确实可以收集它。

顺便说一下,有一天编译器也不会为那些没有生成Foo边界的本地生成字段,这将是一个仍会产生正确行为的优化。如果发生这种情况,那么你的两种方法在基本行为方面会变得更加相似,因此在观察到的行为中更有可能相似。