Something which was as easy as an IO call in the middle of a function running on a thread pool becomes a PITA if the IO call is cleverly async-ized (by wrapping the original sync IO method into a Task.Run(), but actually this is the better case!) ad then removed from the public API... (cannot recall the exact library, but it was a pain, as it was some core stuff, maybe vendored by MS themselves)
Also back then it caused us loosing correlation IDs as those were thread locals in traditional pre-async ASP.Net Core (think version 0.9-2.0 when I worked on that part, then up until core 4 and the async mania continuos broke readable calculation code in LINQ, TPL based performant data pipelines, etc...) and with async execution started to migrate between threads... If you needed high performance you already used threads, because it was not a classic web application, but a specialized optimization algorithm with a web api... and if you need more than CRUD, and use all the cores (and SIMD) to squeeze out all performance you have, you hit these problems right away. These all could be solved, but it actually broke stuff, a lot of stuff, as developers jumped on the fad, and it their sync code was turned into a .ConfigureAwait(false /* Microsoft blogs talk a lot about it without a clear explanation, so every cool kid does this */).GetAwaiter().GetResult()... then you some of your threaded code silently broken if you dare used Thread Locals... Faded memories, but that was when they f'ed up the C# language.
...oooh, that very much sounds like System.Data.SqlClient :)
Indeed, you need a crapload of ConfigureAwait() in desktop UI code, and it infects all underlying libraries that might have to run from a desktop app. The worst thing they ever added to our beautiful language.
I'm using C# for the backend of a web application, not legacy but .NET Core from the start.