Why are async state machines classes (and not structs) in Roslyn?

C#.NetLanguage LawyerRoslyn

C# Problem Overview


Let’s consider this very simple async method:

static async Task myMethodAsync() 
{
	await Task.Delay(500);
}

When I compile this with VS2013 (pre Roslyn compiler) the generated state-machine is a struct.

private struct <myMethodAsync>d__0 : IAsyncStateMachine
{  
	...
    void IAsyncStateMachine.MoveNext()
    {
		...
    }
}

When I compile it with VS2015 (Roslyn) the generated code is this:

private sealed class <myMethodAsync>d__1 : IAsyncStateMachine
{
	...
	void IAsyncStateMachine.MoveNext()
	{
		...
	}
}

As you can see Roslyn generates a class (and not a struct). If I remember correctly the first implementations of the async/await support in the old compiler (CTP2012 i guess) also generated classes and then it was changed to struct from performance reasons. (in some cases you can completely avoid boxing and heap allocation…) (See this)

Does anyone know why this was changed again in Roslyn? (I don’t have any problem regarding this, I know that this change is transparent and does not change the behavior of any code, I’m just curious)

Edit:

The answer from @Damien_The_Unbeliever (and the source code :) ) imho explains everything. The described behaviour of Roslyn only applies for debug build (and that's needed because of the CLR limitation mentioned in the comment). In Release it also generates a struct (with all the benefits of that..). So this seems to be a very clever solution to support both Edit and Continue and better performance in production. Interesting stuff, thanks for everyone who participated!

C# Solutions


Solution 1 - C#

I didn't have any foreknowledge of this, but since Roslyn is open-source these days, we can go hunting through the code for an explanation.

And here, on line 60 of the AsyncRewriter, we find:

// The CLR doesn't support adding fields to structs, so in order to enable EnC in an async method we need to generate a class.
var typeKind = compilationState.Compilation.Options.EnableEditAndContinue ? TypeKind.Class : TypeKind.Struct;

So, whilst there's some appeal to using structs, the big win of allowing Edit and Continue to work within async methods was obviously chosen as the better option.

Solution 2 - C#

It's hard to give a definitive answer for something like this (unless someone from the compiler team drops in :)), but there's a few points you can consider:

The performance "bonus" of structs is always a tradeoff. Basically, you get the following:

  • Value semantics
  • Possible stack (maybe even register?) allocation
  • Avoiding indirection

What does this mean in the await case? Well, actually... nothing. There's only a very short time period during which the state machine is on the stack - remember, await effectively does a return, so the method stack dies; the state machine must be preserved somewhere, and that "somewhere" is definitely on the heap. Stack lifetime doesn't fit asynchronous code well :)

Apart from this, the state machine violates some good guidelines for defining structs:

  • structs should be at most 16-bytes large - the state machine contains two pointers, which on their own fill the 16-byte limit neatly on 64-bit. Apart from that, there's the state itself, so it goes over the "limit". This is not a big deal, since it's quite likely only ever passed by reference, but note how that doesn't quite fit the use case for structs - a struct that's basically a reference type.
  • structs should be immutable - well, this probably doesn't need much of a comment. It's a state machine. Again, this is not a big deal, since the struct is auto-generated code and private, but...
  • structs should logically represent a single value. Definitely not the case here, but that already kind of follows from having a mutable state in the first place.
  • It shouldn't be boxed frequently - not a problem here, since we're using generics everywhere. The state is ultimately somewhere on the heap, but at least it's not being boxed (automatically). Again, the fact that it's only used internally makes this pretty much void.

And of course, all this is in a case where there's no closures. When you have locals (or fields) that traverse the awaits, the state is further inflated, limiting the usefulness of using a struct.

Given all this, the class approach is definitely cleaner, and I wouldn't expect any noticeable performance increase from using a struct instead. All of the objects involved have similar lifetime, so the only way to improve memory performance would be to make all of them structs (store in some buffer, for example) - which is impossible in the general case, of course. And most cases where you'd use await in the first place (that is, some asynchronous I/O work) already involve other classes - for example, data buffers, strings... It's rather unlikely you would await something that simply returns 42 without doing any heap allocations.

In the end, I'd say the only place where you'd really see a real performance difference would be benchmarks. And optimizing for benchmarks is a silly idea, to say the least...

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestiongregkalaposView Question on Stackoverflow
Solution 1 - C#Damien_The_UnbelieverView Answer on Stackoverflow
Solution 2 - C#LuaanView Answer on Stackoverflow