You probably might have heard about Async/Await keywords in other languages such as JavaScript or Dart, and how we use them for asynchronous programming. In this article we'll see how to utilize them and write asynchronous program.
What is Asynchronous Programming?
Computer programs are typically synchronously on a single thread, this implies that that one task must complete before another task runs. This can be fine in small programs that doesn't do any form of I/O.
Asynchronous programming has to do with writing programs that executes multiple tasks concurrently in order not to leave the CPU idle.
Why write Asynchronous code?
Let's assume that we write a program that fetches data from a web service, the program fetches a list of users and a list of latest posts.
def fetch_posts():
result = request('url')
display(result)
def fetch_users():
result = request('url')
display(result)
def main():
fetch_posts()
fetch_users()
main()
In the pseudo-code above, the code runs synchronously. Network requests usually take time to get a response, while the program is waiting a response the CPU lies idle. Let's assume a network request takes 2 secs to resolve it means that the above program will take a little above four seconds to resolve. But if we write the program asynchronously we can run the two tasks(fetch_posts()
and fetch_users()
) concurrently.
Lets see an example that emulates a synchronous approach and another example that take an asynchronous approach.
Synchronous approach
If you run the above program it takes a little over three seconds to complete, that is because the fetch_users()
function waits for the fetch_posts()
function to complete before it starts running.
Let's look at an asynchronous way of writing the same program.
The program above runs in approximately 2 secs as opposed to the 3 secs which the synchronous approach takes. In this case the difference seems small but when working on real world applications, the difference can be huge.
So, we all know how to write synchronous programs, lets see the APIs that enable us write asynchronous code in python.
Writing Asynchronous code in python
Event Loop
In order for us to write asynchronous code, we need an event loop.
An event loop is a programming construct that waits for and dispatches events or messages in a program. The event loop is basically what executes each task in a single threaded application. The event loop can be found in the asyncio
package.
Coroutines
You also need a coroutine. What is a coroutine? A coroutine in python a function or method that can pause it's execution and resume at a later point. Any task that needs to be run asynchronously needs to be a coroutine. You define a coroutine with async def
. Coroutines are awaitable and can not be executed by simply calling the function. Prior to Python 3.5 the async
keyword was not available in python, coroutines were created as a generator functions decorated with @asyncio.coroutine
. You can read more about them here.
Let's see how they all work together.
One on the rules of writing asynchronous code is that coroutines can not contain blocking code. time.sleep(secs)
is a blocking code. Using time.sleep(secs)
in our asynchronous example will make it run synchronously. asyncio.sleep(secs)
is not a blocking code. asyncio.sleep(secs)
represents an asynchronous task that can be awaited. Trying to await a non-awaitable task results to en exception.
One common mistake people new to writing asynchronous in python make is forgetting to await coroutines and other awaitables.
As someone who has written a fair amount of JavaScript, I expected functions marked with async
to behave exactly as the one in JavaScript.
Some differences between JavaScript and Python Async Functions.
JavaScript async functions run on a seperate thread and returns to the main
thread on completion but Python async functions run a single thread and only
switch to another coroutine when an asynchronous I/O operation is encountered.
Consider this program.
In this program I made the event loop switch to another coroutine after every 1000 count. When you run the program, you'll notice that the second coroutine finishes before the first. All coroutine should be awaiting something in order for your code to be asynchronous. Remember everything runs on a single thread, so it's your responsibility to manage it. You can play with the repl to get used to async/await
and asyncio
.
Here is the same program written using different asyncio
API.
From the code above you can see that i didn't call asyncio.get_event_loop()
, this is because asyncio.run()
is equivalent to loop = asyncio.get_event_loop()
and loop.run_until_complete
.
One of the benefits of using asyncio.gather()
over asyncio.wait()
is the ease of getting return values of the coroutines. asyncio.gather()
when awaited returns a list containing all return values.
Check out this Stackoverflow question If you want to know how to get back your return value from your coroutines.
Check this out for a summary of commonly used asyncio
APIs.
asyncio
documentation.
Thanks for reading and I hope you enjoyed the article.
Oldest comments (1)
Awesome post bro. Though you mixed gists and native code highlight.