Omitting async / await

Guriy Samarin
2 min readFeb 12, 2023

Consider following code

async Task Wrapper() => await ActualMethod();

What is the reason not to remove async/await like this?

Task Wrapper() => ActualMethod();

It is bothering me. Why do we create yet another state machine?

  1. Out of habit (called let’s make everything asynchronous)? It’s enough happening in dotnet under the cover and we creating hollow wrappers around it and killing performance at the same time? It is not way to go! Still, we have to be fair with ourselves — it’s not hitting performance hard in most of the cases.
  2. Do we expect major refactoring? And one line becomes 10. Probably we will have using. And every change to Wrapper making it nontrivial will force us to go async. The code of async method is easier to modify. But using async here is against YAGNI and against KISS. I saw too much one-line wrappers to tell you one thing — if you going to need it, then you going to need, you will easily rewrite one-two methods.

Still there are some arguments for going full async all the time. And even if I think YAGNI is the first rule to address — here is the list:

  • Exception handling differs. In async wrapper case all exceptions became asynchronous and will be automatically wrapped in the returned instead of surprising the caller with an actual exception. Consider following codeTask
await DoTestAsync(Asyncy);
await DoTestAsync(Tasky);

async Task Asyncy() => await Method();

Task Tasky() => Method();

async Task DoTestAsync(Func<Task> whatTest)
{
try
{
await whatTest();
}
catch (Exception ex)
{
Console.WriteLine($"StackTrace: {ex.StackTrace}");
}
}

async Task Method()
{
throw new ArgumentException("fake");
await Task.Delay(100);
}

The difference is in the stack trace — in case of task we wouldn’t see line 4 in it. Is it problem? No, but of course it could be surprising for some.

StackTrace:    at Program.<<Main>$>g__Method|0_3() in C:\Users\Guriy Samarin\repos\ConsoleApp2\ConsoleApp2\Program.cs:line 22
at Program.<<Main>$>g__Asyncy|0_0() in C:\Users\Guriy Samarin\repos\ConsoleApp2\ConsoleApp2\Program.cs:line 4
at Program.<<Main>$>g__DoTestAsync|0_2(Func`1 whatTest) in C:\Users\Guriy Samarin\repos\ConsoleApp2\ConsoleApp2\Program.cs:line 12
StackTrace: at Program.<<Main>$>g__Method|0_3() in C:\Users\Guriy Samarin\repos\ConsoleApp2\ConsoleApp2\Program.cs:line 22
at Program.<<Main>$>g__DoTestAsync|0_2(Func`1 whatTest) in C:\Users\Guriy Samarin\repos\ConsoleApp2\ConsoleApp2\Program.cs:line 12
  • Async local's leakage. If you set an async local in a non-async method, it will “leak” out of that call. I don’t know why you want to use async local, but if you do — it’s certainly thing to consider.

Personally, I would use non async wrapper unless async is strictly necessary.

References:

--

--

Guriy Samarin

Software developer at Amazon. Web (mostly backend) development now. My stack — .NET (APS.NET Core MVC).