Roel van Uden Roel van Uden - 1 month ago 7
C# Question

.NET 4.5 Async/Await and the Garbage Collector

I am wondering about the behavior of

async/await
in relation to garbage collecting local variables. In the following example, I have allocated a sizable portion of memory and go into a significant delay. As seen in the code,
Buffer
is not used after the
await
. Will it get garbage collected while waiting, or will the memory be occupied for the duration of the function?

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

Answer

Will it get garbage collected while waiting?

Maybe. The garbage collector is permitted to do so but not required to.

Will the memory be occupied for the duration of the function?

Maybe. The garbage collector is permitted to do so but not required to.

Basically, if the garbage collector can know that the buffer will never be touched again then it can free it at any time. But the GC is never required to free anything on any particular schedule.

If you are particularly concerned, you can always set the local to null, but I would not bother doing so unless you demonstrably had a problem. Alternatively, you could extract the code that manipulates the buffer into its own non-async method and call it synchronously from the async method; then the local becomes just an ordinary local of an ordinary method.

The await is realized as a return, so the local will go out of scope and its lifetime will be over; the array will then be collected on the next collection, which is required to be during the Delay, right?

No, none of those claims are true.

First, an await is only a return if the task is not completed; now, it is of course nigh impossible that Delay will be completed, so yes, this will return, but we cannot conclude in general that an await returns to the caller.

Second, the local only vanishes if it is actually realized in IL by the C# compiler as local in the temporary pool. The jitter will jit that as a stack slot or register, which vanishes when the activation for the method ends at the await. But the C# compiler is not required to do that!

It would seem strange to a person in the debugger to put a breakpoint after the Delay and see that the local has vanished, so the compiler might realize the local as a field in a compiler-generated class that is bound to the lifetime of the class generated for the state machine. In that case it is much less likely that the jitter will realize that this field is never read again, and therefore much less likely to throw it away early. (Though it is permitted to do so. And also the C# compiler is permitted to set the field to null on your behalf if it can prove that you're done using it. Again, this would be weird for the person in the debugger who suddenly sees their local change value for no apparant reason, but the compiler is permitted to generate any code whose single-threaded behaviour is correct.)

Third, nothing requires the garbage collector to collect anything on any particular schedule. This large array will be allocated on the large object heap, and that thing has its own collection schedule.

Fourth, nothing whatsoever requires there to be a collection of the large object heap in any given sixty second interval. That thing need never be collected if there is no memory pressure.