18

Since the inception of .NET Core, console apps, function apps, ASP.NET etc. are not using synchronization context in async methods (so they're synchronizing straight to Thread Pool).

This means that using .Result() or .Wait() after async method doesn't cause deadlock any more, and only affects performance (reserving a thread), which is mostly not a concern in small single-threaded business applications that only read and write data from one place to another.

Now most of our developers are happily using .Result() and .Wait() because it looks much nicer than the separate await keyword. And the project sizes are so small that they are the sole developers in those projects.

How can I justify using await keyword when other developers do not see any incentives in using it? I can see that this behaviour can lead to problems in larger projects.

EDITED the question. See the comments.

7
  • 4
    This is a very strange question. You begin by saying "let's ignore all the benefits" and then conclude "there are no benefits" and then ask "how can I justify doing something with no benefits?". If you don't have any performance problems that are solved by asynchronous workflows then why are you using asynchronous workflows in the first place? Asynchronous workflows were invented to solve performance problems with high-latency operations; if you don't have those problems, don't use the tool intended to mitigate them! Sep 27, 2019 at 19:17
  • 10
    All that said, I strongly discourage your developers from writing any more asynchronous workflows at all until they understand how they work. Getting an asynchronous program correct is fundamentally different from getting a synchronous program correct and your developers are apparently proceeding as though their asynchronous program is a synchronous one. This is not going to go well. Sep 27, 2019 at 19:20
  • 5
    By way of analogy, suppose I asked you "we all know that most people are never in car accidents, and cars are really safe these days, and seatbelts only affect safety, which is no longer a concern. My employees have decided to stop wearing seatbelts and none of them have died yet. Given that seatbelts are useless, how can I justify using them when they are of no benefit?" How would you even begin to answer such a question? Sep 27, 2019 at 19:23
  • 1
    In the last sentence, I should have stated that how can I justify using await keyword when other developers do not see obvious benefit in using it.
    – kor_
    Sep 30, 2019 at 7:46
  • 3
    "If you don't have any performance problems that are solved by asynchronous workflows then why are you using asynchronous workflows in the first place?" -- Because most libraries today only offer Async version, so there's no other option.
    – kor_
    Sep 30, 2019 at 7:49

3 Answers 3

24

You have missed the point of async/await

It wasn't created to frustrate developers with deadlocks. It was created to free up Processing power while we wait for a non CPU based task to complete.

The lack of synchronisation context in dotnetcore might save you from unexpected deadlocks, but it also means you have to worry about thread starvation.

Everytime you block a thread with task.Wait() or task.Result() thats one less Thread that your app could be using to do stuff with.

Using await frees up that Thread to be used on other tasks.

Where you would have got a deadlock before, you now run out of threads.

https://docs.microsoft.com/en-us/aspnet/core/performance/performance-best-practices?view=aspnetcore-3.0

I'm going to risk a thought experiment to demonstrate the point without actually testing the code. Always a risk because things are never as simple as they seem.

Let's say we have an ecommerce site. customer places orders via an api call to the backend. the api saves the order to a database, but some orders need fraud checking via an external api which is sometimes v slow, say 60sec.

During normal operation you get a couple of orders a minute and the site operates fine. During your christmas sale however you get an order per second.

You are running on a 4 core machine, if your API blocks on the fraud check you are down to 3 threads, if another 3 fraud check orders come in before the first completes the orders start queuing up. The API is locked.

A slow order completes and frees up a thread, but now you have a backlog of requests to catch up with and each one of those requests has a timeout.

So at peak times customers starts seeing "Error could not place order : timeout"

If you await the fraud call instead then the thread is immediately freed up when the call is made and can be servicing requests until the response comes back. You only have 1 order a second and 4 threads are plenty.

12
  • 2
    To be fair, I don't think the OP missed the point. They explicitly stated that they understood using Result/Wait would decrease performance, they were just looking for a way to articulate to other developers why it is important to use await in their own code if they are not personally worried about performance at the time. Sep 27, 2019 at 14:57
  • 2
    well maybe the missed point is that thread starvation isn't just a 'runs a bit slow' performance issue its a 'app is locked up' performance issue
    – Ewan
    Sep 27, 2019 at 15:07
  • 2
    I.e. it's a correctness issue, not a performance issue.
    – Caleth
    Sep 27, 2019 at 15:11
  • 1
    The thought experiment makes it seem like the number of Tasks you can run is tied to the number of CPUs in the machine, but it is actually tied to the thread pool size, and that only applies if you're actually using await. If you're not using await, then calling Result/Wait will lock the thread until complete, so unless something else in your application is creating other threads, it may only take one usage of Result or Wait to lock the application. One of many reasons why standardizing on using await is the right answer. Sep 27, 2019 at 16:09
  • 2
    @Ewan The default MinThreadPoolSize is equal to the number of cores, but the default MaxThreadPoolSize is based on many factors, such as the available virtual address space, and there is no easy way to know what it will be on any given system. Sep 27, 2019 at 20:05
4

The biggest factor, in my opinion, is that using the Result/Wait members causes the method you're writing to be synchronous rather than asynchronous. That is, you are explicitly deciding to write a synchronous function and changing the semantics of methods you're calling to fit that model. Doing that means every method that might ever need to call your method will need to do it in a synchronous way, rather than being able to do it asynchronously, which it could if you used the await keyword and allowed the asynchronous behavior of the other functions you're calling to propagate.

This is important because you never know how a method you write, especially a public one, might be used later by another developer. It may not be in a place where threading and performance are of any concern now, but they may well be relevant somewhere that a future consumer is using the method, and you won't know that at the time you're writing it. Unless you absolutely need your method to be synchronous for some reason, the best option is to allow the asynchronous nature of other methods you're calling to propagate via your method as well, by using await and making your method async.

0

The "obvious benefit", as you note, is that it does effect performance. It's not clear from your question what async methods you are using, but if performance is not a concern, you could simply use the the synchronous versions of those methods (which are available in many APIs) and not deal with asynchrony at all. If this is in the context of ASP.NET however, where there is implicit concurrency happening (handling multiple requests), it would be advisable to go with the async/await to not impede performance of request handling.

1
  • 1
    Yes but in this case we don't have synchronous versions available, so async is the only option and this is what makes it problematic. Developers are used to synchronous workflow and are just "trying to make it work" with .Result() and .Wait() with knowing what it actually does. What makes it worse is that the apps mostly actually work correctly (albeit slowly/tying up threads) and it's very hard for someone to come in and say "fix your program" without appearing as a dick.
    – kor_
    Sep 30, 2019 at 8:02

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.