Asynchronous Programming in Python using Thread

There are many ways to do asynchronous programming in Python, but the one I’ll cover here is the probably the first option that most Python programmers will use, or is taught —- and that is using the threading module.

Before we dive into coding, let’s talk about Synchronous and Asynchronous processing (or in our case, programming):

Synchronous vs Asynchronous processing

You can Google this for details, but here’s how I think of them:

Synchronous (aka blocking)

Synchronous processing (or in our case programming) is executing a routine one after the other. It is sometimes referred to as “blocking” since a line of code won’t execute until the previous line completes.

The line at the counter of a fast food chain is a good example of synchronous processing. Your orders can’t be processed by the counter until all the people in front of you have their orders taken.

This is what we all learn when we started programming and most programmers stay in this category since most of them don’t have a need to do otherwise.

Asynchronous (aka non-blocking)

Asynchronous processing (or in our case programming) is executing routines concurrently. It’s not always parallel, but the a line of code doesn’t have to wait for the previous line to complete for it to start.

Some restaurants have waiters to go to your table and ask for your orders. If you’re ready, they’ll take your order. If not, they’ll go to another table to get theirs, and come back to you after. It’s almost the same as asynchronous programming.

Again, it doesn’t always mean parallel processing/programming. It just means a task can be started without waiting for the previous one. To make our restaurant example parallel, each table would have a waiter, all taking orders at the same time. In programming, it’s almost always multi-processing or running on multiple CPUs.

This one requires some adjustments not only in codes but in the mindset as well and is often the reason why very few programmers ever do it or even understand it. Very simply, in synchronous programming, you call a function and wait for its result. This isn’t the case in asynchronous programming as the next line of code executes immediately and you need a mechanism to get the result later or in the future.

Comparing the two approaches

In order to see the value of asynchronous (non-blocking) processing or programming, we’ll first take a look at a typical synchronous (blocking) code and get the total elapsed or execution time.

Example: Synchronous Programming

Let’s write a program that calls web services and time them. We’ll just simulate the call via a delay (time.sleep) so we can focus on the actual topic).

Total Elapsed Time = 40 seconds

Running the synchronous code, we can see that total elapsed time for executing all three web services is 40 seconds — which is expected, since that is the sum of all simulated elapsed time of 12, 13 and 15.

But what if you are calling more than three web services or each one takes longer time? That would surely increase the execution time of your code and the waiting time of the user or client application. And it doesn’t have to be a web service call: it could be a database transaction, reading and/or writing to a file, etc.

Enter: Asynchronous Programming

Using the code above as baseline or starting point, first thing we do is import the “threading” module. Again, there’s more than one way of doing concurrent programming in Python but we’ll use the most obvious and arguably beginner-friendly option.

Next, we modify the call_a_webservice function to accept another parameter that points to a function object. We also added the individual timing call here (but only if you want to measure that, otherwise it’s not required).

We then create the callback function that we’ll pass on to the modified function above. This new function will be called when the web service call has completed.

Why do we need a callback function?

Remember that asynchronous means not waiting for the previous call to complete before starting the next one? This is the mechanism that will inform us if and when that asynchronous call has completed.

Lastly, we modified the body of the loop to run the call_a_webservice on a different thread. We then wait for all the threads to complete via the “join” method.

Total Elapsed Time = 15 seconds

Running the asynchronous code, we see that it only took 15 seconds for all the web service calls to complete.

Here are some major points to take note of:

  • All three web service calls started the same exact time (no waiting like the synchronous version)
  • Total elapsed time is only equal to the longest execution time (which is 15 seconds)

Conclusion

As you can see, the adjustment from doing synchronous to asynchronous programming is not that small or simple. It usually takes some time to even understand how it works, let alone get used to it (to the point where you can code if confidently).

But, you should also see the value of knowing this approach especially if you want to scale you application like delegating long-running tasks to background thread, or just plainly making your application faster and/or more responsive.

Thank you and see you on the next blog!

Leave a Comment