Best Practices: When to (NOT) Use Asynchronous Programming

Now that it’s easy in c#, I’m a big fan of asynchronous and parallel programming. You can make multi-step code go multiple times faster, and prevent the evil .NET ThreadPool from blocking and throttling your code all in one step.

Although there is some great performance advice in articles such as MSDN’s Async Performance: Understanding the Costs of Async and Await, I’d like to go a little deeper on some rationale on when to use async and when not to use async.

The best thing to do is to prototype your solution and do performance testing, but if you don’t have time to do that, then these guidelines might help you make a decision. (If you have something to add, or dispute some of the logic, please comment at the end.)

Generally, if you are writing NEW code, the choice is pretty simple:

  1. If you are making a .NET API call that ends in Async, you PROBABLY SHOULD use async.
  2. If you can do things in parallel, you SHOULD use the Task Parallel Library or async code to do more things at a time.
  3. If you have a main UI, ASP, or Service thread that can go do work for someone else while you are doing CPU work or waiting, you PROBABLY SHOULD use async.
  4. Otherwise, you PROBABLY SHOULD NOT bother.

If rules 1-3 somewhat apply, but you are still on the fence, then try this rule to help you out:

  1. If your team is comfortable writing, reading and debugging async code, go ahead. The tools are good enough now, that the overhead is probably worth it. Otherwise, don’t bother.

Some cases for async are easy:

  • Web Service calls – particularly if you have no idea how long they are going to take
  • Database calls – particularly if they could take a long time
  • Parallelism – doing more than one thing at a time
  • CPU-bound tasks – moving them to a background thread

Some cases against async are easy:

  • Short calculations or quick-running methods, e.g. writing to the Console for logging
  • Synchronized Contexts

So that leaves us with the fuzzy area to discuss. I have a scenario where making the code asynchronous would make the code cleaner and more maintainable. In this case:

  • there is EXISTING code
  • calling a DATABASE
  • but the queries SHOULD BE QUICK
  • and there isn’t much else to do

Async or not async?

I put a lot of thought on this borderline case. My rationale is as follows:

  • There isn’t anything to do in parallel
  • The rest of the service is hitting the SAME database with the SAME queries, so serving another request probably won’t have much benefit
  • There is a SINGLE database behind the service, so we wouldn’t benefit from querying other resources while waiting
  • The performance for sync/async was about the same in benchmarks.
  • The existing code works.

There isn’t a performance benefit for going async here. If this was NEW code, I would probably write it asynchronously (because it’s easy now) to get the benefit of eventually calling different services or databases. But this is existing working code, so I need a bigger benefit than just cleaner code. The aha moment for me here was that service is calling the same backend resource for the same queries, so the bottleneck isn’t at this layer. There’s no point in optimizing code that isn’t the bottleneck.

6 thoughts on “Best Practices: When to (NOT) Use Asynchronous Programming”

  1. I’m not actually a C# user, but nice post.

    I’ve spent some time thinking about that borderline case too (in other languages/environments), and have thought of an edge to your edge case, that I’m curious what you think about.

    What if the database server has a whole bunch of CPU’s and knows how to use them? Then _hypothetically_, the app code could be the bottleneck even though the rest of the service is hitting the same database querries, serving another request still _could_ be possible, because the database itself can handle multiple db requests concurrently on multiple CPU’s.

    This is probably mostly just hypothetical, and I think probably doesn’t change your basic calculus that it’s not worth doing unless it’s easy and cheap to do. But curious what you think about that. I haven’t actually performance tested it (in C# or any other environment, with any particular db, the particular db will matter), just thinking through it.

    1. That’s a great case. The best thing to do is to do performance testing. In the case that I came across yesterday, there was plenty of capacity on the database (SSDs + RAM + CPUs + Network), and we weren’t really hitting any bottlenecks in the Service tier either for our projected load (the .NET ThreadPool was creating enough threads for the workload and there was enough RAM for stacks). So there was no need to additional optimizations. When we scale closer to the capacity of the system, something will break, and at that point we will fix the slowest part.

  2. How does this work with resources that are pinned to the lifetime of a request in the app, via a container or somesuch? For example: I have an mvc app that includes an IHttpModule that, at the beginning of a request, sets aside nhibernate DB sessions for a number of databases that can be used in the scope of the request.

    In every single non trivial mvc/monorail app I’ve looked at or worked on, the existing pattern has been to store these dependencies in the session cache or thread-static storage. Does this hold up when we switch to an a sync model or does the underlying infrastructure have to be refined to deal with this? Is the async method pinned to resume its workload on the same thread? What some other workload running while another is waiting? Can they step on each others data?

    Apologies for the barrage of questions. Just curious about the feasibility for the apps that I have to maintain.

    1. If you are already have resources shared across multiple requests, then you are already dealing with parallelism in one way or another. For example, when you put data in a session state, your framework will generally synchronize access to the session data to prevent more than one thread from accessing the session at a time. If the framework doesn’t do that, then you are responsible for making sure that your data access is thread-safe. Most frameworks provide some form of locking/mutex system to allow you to manage concurrent access.

      I think there is an important distinction to be made between “asynchronous” and “parallel”. “Asynchronous” generally means “don’t block while waiting for something to complete”. You can code using async, while keeping a single thread at a time accessing your data. Then you don’t have to worry about concurrent access. “Parallel” generally means “doing more than one thing at a time”. When writing code that does work in parallel, you definitely have to worry about concurrent access to your data.

      (I think I’ll write about this in more detail soon.)

Leave a Reply