.NET 4.5 Async/Await和垃圾收集器

Roe*_*den 19 .net c# garbage-collection async-await

我想知道async/await与垃圾收集局部变量有关的行为.在下面的例子中,我已经分配了相当大一部分内存并进入显着延迟.如代码所示,Buffer之后不使用await.它会在等待时收集垃圾,还是在功能持续时间内占用内存?

/// <summary>
/// How does async/await behave in relation to managed memory?
/// </summary>
public async Task<bool> AllocateMemoryAndWaitForAWhile() {
    // Allocate a sizable amount of memory.
    var Buffer = new byte[32 * 1024 * 1024];
    // Show the length of the buffer (to avoid optimization removal).
    System.Console.WriteLine(Buffer.Length);
    // Await one minute for no apparent reason.
    await Task.Delay(60000);
    // Did 'Buffer' get freed by the garabage collector while waiting?
    return true;
}
Run Code Online (Sandbox Code Playgroud)

Eri*_*ert 25

它会在等待时收集垃圾吗?

也许.垃圾收集器是允许的,但不是必需的.

内存是否会在函数期间被占用?

也许.垃圾收集器是允许的,但不是必需的.

基本上,如果垃圾收集器可以知道缓冲区永远不会被再次触摸,那么它可以随时释放它.但GC从不需要在任何特定的时间表上释放任何东西.

如果您特别担心,您可以随时将本地设置为null,但除非您明显遇到问题,否则我不会这么做.或者,您可以将操作缓冲区的代码提取到其自己的非异步方法中,并从异步方法中同步调用它; 然后本地变成普通方法的普通本地.

await实现为return,所以当地将走出去的范围,其寿命也就结束了; 然后在下一个集合中收集数组,这需要在期间Delay,对吗?

不,这些说法都不是真的.

首先,await只有return当任务没有完成时才是a ; 现在,它当然几乎不可能Delay完成,所以是的,这将返回,但我们不能总体上得出结论,await返回调用者.

其次,如果在IL中由C#编译器在临时池中实现本地实现,则本地只会消失.抖动将jit作为堆栈槽或寄存器,当方法的激活结束时,抖动消失await.但是C#编译器不需要这样做!

调试器中的人在Delay看到本地已经消失之后放置一个断点似乎很奇怪,因此编译器可能会将本地实现为编译器生成的类中的一个字段,该类绑定到生成的类的生命周期对于状态机.在这种情况下,抖动不太可能意识到该字段永远不会被再次读取,因此不太可能提前将其丢弃.(虽然允许这样做.并且C#编译器也可以代表你将该字段设置为null,如果它可以证明你已经完成了使用它.再次,这对于调试器中的人来说是奇怪的突然看到他们的本地更改值没有明确的原因,但允许编译器生成任何单线程行为正确的代码.)

第三,没有什么要求垃圾收集器在任何特定的时间表上收集任何东西 这个大型数组将在大型对象堆上分配,并且该东西有自己的收集计划.

第四,没有任何东西要求在任何给定的60秒间隔内存在大对象堆的集合.如果没有记忆压力,就不需要收集那件东西.


svi*_*ick 7

Eric Lippert所说的是真的:C#编译器对IL应该为该async方法生成什么有很大的余地.所以,如果你问的是规范对此有何看法,那么答案是:在等待期间阵列可能有资格收集,这意味着它可能被收集.

但另一个问题是编译器实际上做了什么.在我的计算机上,编译器生成Buffer一个生成状态机类型的字段.该字段设置为已分配的数组,然后再也不会设置它.这意味着当状态机对象执行时,该数组将有资格进行收集.并且该对象是从continuation委托引用的,因此在等待完成之后它才有资格进行收集.这一切意味着数组在等待期间符合收集资格,这意味着它不会被收集.

还有一些说明:

  1. 状态机对象实际上是一个struct,但它通过它实现的接口使用,因此它表现为用于垃圾收集的引用类型.
  2. 如果您确实确定不会收集数组的事实对您来说是个问题,那么将本地设置为null之前可能是值得的await.但在绝大多数情况下,您不必担心这一点.我当然不是说你应该定期将当地人设置到null以前await.
  3. 这是一个非常实现的细节.它可以随时更改,编译器的不同版本可能会有不同的行为.