Say you have:
readonly struct MyStruct : IDisposable
{
// ... fields, IDisposable implementation and so on ...
}
async Task test()
{
using var myStructInstance = GetMyStruct(); // Some external function which creates and returns an instance of MyStruct
await SomethingElse();
}
What is the impact on the overall GC behaviour of the system, depending on whether the subsequent async call executes synchronously, or truly asynchronously?
In particular,
SomethingElse()
returns synchronously?As you can probably tell, my general aim is to figure out if locally used value types in async functions could potentially contribute to GC pressure, or if they generally have minimal to no impact.
Or, since technically its lifetime is strictly tied to the generated async state machine, would it be part of the underlying memory layout of the state machine itself
In current compiler implementation the local variables used after the await will be stored as part of the async state machine. You can use https://sharplab.io/ to dabble with the decompilation.
Consider the following code:
public class C {
async Task test()
{
var o = new object();
var o1 = new object();
var myStructInstance = new MyStruct(1);
Console.WriteLine(o1);
await Task.Yield();
Console.WriteLine(o);
Console.WriteLine(myStructInstance.I);
}
}
readonly struct MyStruct
{
public MyStruct(int i) => I = i;
public int I {get;}
}
This will lead to o
and myStructInstance
stored in the fields (but not o1
):
[StructLayout(LayoutKind.Auto)]
[CompilerGenerated]
private struct <test>d__0 : IAsyncStateMachine
{
// ...
private object <o>5__2;
private MyStruct <myStructInstance>5__3;
}
So no, myStructInstance
should not be boxed as a separate object.
my general aim is to figure out if locally used value types in async functions could potentially contribute to GC pressure, or if they generally have minimal to no impact.
In theory yes - if you drill down into decompiled state machine you will see that at some point it can be "boxed" into internal IAsyncStateMachineBox
interface (it happens when task has not finished, AsyncTaskMethodBuilder.AwaitUnsafeOnCompleted
is called which will result in the GetStateMachineBox
call), so the whole statemachine will be placed on heap and the size of the state machine depends on captured variables. So yes some effect is there but I would argue that in most cases it is negligible.