Rise of the IAsyncStateMachines

Posted on June 13, 2016 · 2 mins read · tagged with: #async #async await

Whenever you use async/await pair, the compiler performs a lot of work creating a class that handles the coordination of code execution. The created (and instantiated) class implements an interface called IAsyncStateMachine and captures all the needed context to move on with the work. Effectively, any async method using await will generate such an object. You may say that creating objects is cheap, then again I’d say that not creating them at all is even cheaper. Could we skip creation of such an object still providing an asynchronous execution?

The costs of the async state machine

The first, already mentioned, cost is the allocation of the async state machine. If you take into consideration, that for every async call an object is allocated, then it can get heavy.

The second part is the influence on the stack frame. If you use async/await you will find that stack traces are much bigger now. The calls to methods of the async state machine are in there as well.

The demise of the IAsyncStateMachines

Let’s consider a following example:

[code language=”csharp”] public async Task A() { await B (); }


Or even more complex example below:

[code language="csharp"]
public async Task A()
{
    if (_hasFlag)
    {
        await B1 ();
    }
    else
    {
        await B2 ();
    }
}

What can you tell about these A methods? They do not use the result of  the Bs. If they do not use it, maybe awaiting could be done on a higher level? Yes it can. Please take a look at the following example:

[code language=”csharp”] public Task A() { if (_hasFlag) { return B1 (); } else { return B2 (); } }

```

This method is still asynchronous, as asynchronous isn’t about using async await but about returning a Task. Additionally, it does not generate a state machine, which lowers all the costs mentioned above.

Happy asynchronous execution.


Comments

[…] Rise of the IAsyncStateMachines by Szymon Kulec. […]

by The week in .NET – 6/21/2016 | 神刀安全网 at 2016-06-22 02:11:32 +0000

[…] Rise of the IAsyncStateMachines by Szymon Kulec. […]

by The week in .NET – 6/21/2016 | Tech News at 2016-06-22 05:20:01 +0000

One important note: do not directly return a Task in this way from within a using block, or the disposables that may be required by the asynchronous task (e.g. dataReader.ReadNextAsync()) will be disposed and weird stuff will ensue,

by Mark at 2016-06-22 09:52:21 +0000

Thank you for your comment Mark. Of course, in cases described by you the disposable isn't managed by the async state machine and this approach should not be followed.

by Szymon Kulec 'Scooletz' at 2016-06-22 10:35:36 +0000

[…] the last post I’ve shown some optimizations one can apply to reduce the overhead on creating asynchronous […]

by Task.WhenAll tests | Extreme .NET programming at 2016-06-15 07:00:10 +0000

Perhaps this could be an optimization made by the compiler?

by Piotr at 2016-11-21 14:58:14 +0000

This optimization removes the frame from the async stack if an exception is thrown, so you doing it explicitly seems ok to me.

by Szymon Kulec 'Scooletz' at 2016-11-21 16:53:28 +0000

The downside of this is that you lose important stack trace information you will no longer know that B was called through A if an exception is thrown from B. That might be acceptable in certain situations and, therefore, worth the perf tradeoff, but everyone should be aware of the consequence.

by Drew Marsh (@drub0y) at 2016-06-23 21:33:57 +0000

[…] La venue de IAsyncStateMachines et de l’exécution asynchrone de votre code. […]

by Les liens de la semaine – Édition #190 | French Coding at 2016-06-27 11:01:25 +0000

"If you take into consideration, that for every async call an object is allocated, then it can get heavy." The async state machines are highly optimized to avoid as much allocation as possible. While it does allocate objects, usually , the underlying I/O operation being done will always be more expensive than the state machines allocation. That is, if you're using async code on a hot path, you definitely should think if it's possible to defer the awaiting to the highest stackframe to save allocation.

by yuvalos at 2016-06-23 07:38:11 +0000