Eliding the await keyword can lead to a less traceable stacktrace due to the fact that every Task which doesn’t get awaited, will not be part of the stack trace.
System.Exception: Hey
at Program.<<Main>$>g__ThrowExceptionAsync|0_1()
at Program.<<Main>$>g__DoWorkWithoutAwaitAsync|0_0()
at Program.<Main>$(String[] args)
💡 Info: Eliding the async keyword will also elide the whole state machine. In very hot paths that might be worth a consideration. In normal cases one should not elide the keyword. The allocations one is saving is depending on the circumstances but a normally very very small especially if only smaller objects are passed around. Also performance-wise there is no big gain when eliding the keyword (we are talking nano seconds). Please measure first and act afterwards.
💡 Info: Eliding the async keyword will also elide the whole state machine. In very hot paths that might be worth a consideration. In normal cases one should not elide the keyword. The allocations one is saving is depending on the circumstances but a normally very very small especially if only smaller objects are passed around. Also performance-wise there is no big gain when eliding the keyword (we are talking nano seconds). Please measure first and act afterwards.
The problem with async void is first they are not awaitable and second they suffer the same problem with exceptions and stack trace as discussed a bit earlier. It is basically fire and forget.
❌ Bad Not awaited
publicasyncvoidDoAsync(){awaitSomeAsyncOp();}
✅ Good return Task instead of void
publicasyncTaskDoAsync(){awaitSomeAsyncOp();}
💡 Info: There are valid cases for async void like top level event handlers.
Using blocking calls instead of await can lead to potential deadlocks and other side effects like a poor stack trace in case of an exception and less scalability in web frameworks like ASP.NET core.
Favor GetAwaiter().GetResult() over Wait and Result¶
Task.GetAwaiter().GetResult() is preferred over Task.Wait and Task.Result because it propagates exceptions rather than wrapping them in an AggregateException.
Don’t use Task.Delay for small precise waiting times¶
Task.Delay‘s internal timer is dependent on the underlying OS. On most windows machines this resolution is about 15ms.
So: Task.Delay(1) will not wait one millisecond but something between one and 15 milliseconds.
varstopwatch=Stopwatch.StartNew();awaitTask.Delay(1);stopwatch.Stop();// Don't account the Console.WriteLine into the timerConsole.WriteLine($"Delay was {stopwatch.ElapsedMilliseconds} ms");
✅ Good When tasks or their data is independent they can be awaited independently for maximum benefits. The following code will run roughly 0.5 seconds.
Since C# 8 you can provide an IAsyncDisposable which allows to have asynchrnous code in the Dispose method. Also this allows to call the following construct:
In this example CreateDbContextAsync uses the ConfigureAwait(false) but not the IAsyncDisposable. To make that work we have to break apart the statment like this:
The last part has the “ugly” snippet that you have to introduce a new “block” for the using statement. For that there is a easy workaround:
varblogDbContext=awaitdbContextFactory.CreateDbContextAsync().ConfigureAwait(false);awaitusingvar_=blogDbContext.ConfigureAwait(false);// You don't need the {} block here