Writing asynchronous code is popular these days. Look at this search trend from the last 5 years.
I have the feeling that the number of tutorials on the internet explaining
asynchronous code has increased quite a bit since Python started supporting the
async
/await
keywords. Even though Python has always had support for running
asynchronous code using the asyncore
module (or using libraries like
Twisted), I don't think that asyncore
was used as much as the new asyncio
.
This is a pure gut feeling though; I have no numbers to back that claim up.
Anyway, asyncio
makes it slightly easier to write asynchronous code. Slightly,
because I don't know if I can call the API as intuitive, or dare I say,
"Pythonic". This article does a much better job of explaining why asyncio
is
what it is.
Even if we put asyncio
aside, I don't think asynchronous code is ever
easy. There's just so much going on under the hood that it's difficult to keep
your head from spinning, before you can actually get to writing the application
logic.
But that's not what this blog post is about. This blog post is about how not everything needs to be async. And that if some code you're working on absolutely necessarily must be async, then why it makes sense to stop for a minute and consider the consequences of introducing this extra level of complexity.
This has nothing to do with Python, or asyncio
, or any async framework in
general. All I want to say, is, if you think you want to write asynchronous
code, think twice.
Synchronous is much simpler
Synchronous code is simple to write. It's also much easier to reason about, and it's lot less likely to contain concurrency or thread-safety bugs than asynchronous code. As programmers, our job is to solve business problems reliably in the least possible time. Synchronous code fits that criteria quite well. So if I'm given a choice between writing synchronous or asynchronous, I can say with a reasonable amount of confidence that I'll prefer synchronous.
Would async really help?
Next, if asynchronous code is absolutely required, it makes sense to think about what it's going to do underneath, and what performance gains it's going to bring.
For instance, if you're writing a web request handler which calls out a few external APIs and combines those responses to finally return a response to your user, yes, asynchronous code would absolutely help. The time that the external resources make your request handler wait can be used to serve other user requests.
On the other hand, if your request handler is fetching a few rows from a database server that's running on the same machine as the app server, it's not going to make that much of a difference if it were async.
Is it safe?
Often times we end up using abstractions that hide away the implementation details and provide a nice API for us to work with. In these cases, it's important to know what exactly is being hidden, or how that abstraction is working underneath.
For example, Python provides an abstraction called ThreadPoolExecutor
, which
allows you to run functions in separate threads (there is also
ProcessPoolExecutor
which lets you separate things on a process-level).
The way this works is that you submit a callable to the pool, and the pool
returns a Future
object immediately. And when the function has finished
running, the results (or the exception) would be stored in this future object.
Since there are Future
objects involved (which you can await
on), it can be
tempting to use this abstraction to write async code. But because now there are
multiple threads involved, it's not that simple anymore. The functions being
submitted to the thread pool should now only make use of resources that are
thread-safe. In case two callables are submitted to the pool, both referencing a
particular object which is not thread-safe, there's potential for weird
concurrency bugs.
Closing thoughts - async is useful (and cool), but there is a time and place for everything. It may result in an increased CPU utilization without necessarily bringing speed improvements, so it's helpful to keep that in mind when writing async code.