Introducing asyncio
00:00
In the previous lesson, I showed you the danger of race conditions. In this lesson, I’ll be discussing the asyncio Python library.
00:09
asyncio was introduced in Python 3.4, and there are changes in the library as recent as 3.7.
00:16
The threading library shown two lessons ago is dependent on threads supported by the operating system. asyncio is Python-specific.
00:26 It is independent of the operating system threading mechanism. This gives you finer-grained control over your concurrency.
00:34 The concurrency model uses something called event loops and coroutines. There is a main loop that happens inside of Python, which controls the order of things happening, and your coroutine is the code that is being run. Inside of the code, you signal that you no longer need control and you give up execution.
00:53 The event loop then schedules the next coroutine. This is cooperative concurrency. If one of your tasks doesn’t give up its turn, the other tasks starve.
01:03 But seeing as all of this is happening inside of your Python code, you aren’t fighting with some other program having to be cooperative. It’s your own code you are in control over, so as long as you write good coroutines you won’t have a problem.
01:17
asyncio is built on two keywords: async and await. async indicates that code is to be run asynchronously, and the await keyword is the cooperative signal that your coroutine is willing to give up execution control.
01:33
The biggest challenge with asyncio is that it’s still fairly new. To take advantage of it, the libraries you are using have to support it. As an example, the requests library doesn’t currently support asyncio.
01:45
The threaded web fetcher that I showed you in an earlier lesson needs to be rewritten using a different library to achieve the same thing in asyncio.
01:55
aiohttp is a library similar to requests, but it is asyncio aware. I’ll be using this library in the following examples. You’ll need to do a pip install to make it available to yourself.
02:08 It’s not part of the Python standard library.
02:12
This is the asyncio version of the web downloading program that I showed you earlier in both synchronous and threaded varieties. The concept’s the same.
02:23 A web page at Jython and one at Real Python are getting downloaded 80 times each. A couple of the imports have to be different. First, I need asyncio to well, use asyncio.
02:34
Second, as I mentioned before, although the requests web library is great, it doesn’t work with coroutines, so I’m using a-io-http instead.
02:44
Line 6 is the definition of download_site(). This is the part of the code that gets run concurrently. The function takes two parameters, a session, which is an object from the a-io-http library and the URL to get downloaded.
02:59 Notice the async keyword at the beginning of the function indicating that it’s going to operate asynchronously. Line 7 is a context manager Using the session.
03:09
Objects .get() method. This is what actually fetches the webpage. The context manager is also marked as async. Inside of the body, The response object will be populated containing the webpage downloaded. Like before I’m printing out a J or an R depending on the URL and printing something to the screen so you can see the downloads happening.
03:30
Line 11 is the download_all_sites() method, similar to the one in the threading code.
03:36
This version of the function also needs to be declared as async since it calls asynchronous code within it. Line 12 is what creates the session object that gets passed into each of the download_site() coroutines.
03:49
The asyncio library divides things that are being executed into objects, sometimes known as tasks. You can examine a task when it is running and their results can be stored there for when they are complete.
04:01
Line 13 creates a list for these tasks to be stored. Line 15 is a future. This is similar to the pool concept In the threading code. The download_site() method gets called, but as it’s asynchronous, it likely won’t complete, so a handle to it gets returned.
04:18
The download_site() method is the coroutine. This is where the execution happens.
04:23 Then I take the resulting task and store it in a list so that anytime I can get ahold of it to see if it is running or not and whether something inside of it has failed.
04:33
Line 18 is the gather() method. This is similar to .join() in the threading example. asyncio waits here until all the tasks are complete.
04:42
The list of tasks is given to gather and I’ve set return_exceptions, which means anything that goes wrong in the task gets stored in the task object.
04:50 I’ll come back to this later. Lemme just scroll down.
04:55 The list at line 21 is the same as before,
04:59
and then once again, I’m printing out a message and starting a timer. Line 28 is what kicks off the event loop causing the asynchronous methods to run. Since download_all_sites() has a call to gather at the bottom.
05:11 This method won’t return until all the downloading is complete and the coroutines have returned. Alright, well let me give this a go.
05:23 Once again, concurrency is significantly speeding up the program. Notice the pattern of J and R like the threading library, you have an inconsistent back and forth between the downloads.
05:34 It isn’t just J-R-J-R-J-R like when synchronous. Since the event loop determines when the futures happen and how much time gets allocated to each of the coroutines.
05:47
Let’s drill down a little more on the call to gather(). Notice the return_exceptions parameter. In the code, I set this to True. The value of this parameter indicates what to do if something goes wrong inside of the coroutine.
06:02
Your choices are return_exceptions=False, which means the exception will filter up and stop your program, similar to a synchronous execution.
06:10
This is the default behavior or you can register the exception inside of a task object. return_exceptions=True means to return the exception into the task object.
06:23 There’s pros and cons between these different mechanisms. The default allows you to catch problems inside of your code because the exceptions happen like you would expect them to.
06:33 But what if the exception is being caused by just one execution of one of the coroutines? Do you want everything else to die? And that’s why this is a choice.
06:43
By selecting return_exceptions=True, all of the tasks can operate and then you can introspect them afterwards to see whether or not some succeeded or failed.
06:53
If you’re creating a new asynchronous program, I would recommend leaving the default behavior on until you’ve debugged the code, then switch to return_exceptions=True while you’re running your actual code so that you aren’t interrupted.
07:06
If something goes wrong inside of only one of your coroutines. At any time during execution, you can call the .result() method on the Task object.
07:17 If the task executed successfully, then you will get back whatever the coroutine returned.
07:24
If the task caused an exception, then that exception will be fired. When you call the .result() method, you can also request that the event loop cancel running tasks.
07:35
If you’ve done that, then this Task.result() method will return the CancelledError exception. If you call the method too early and the task hasn’t finished operating and hasn’t been canceled, you’ll get an InvalidStateError.
07:48
There are other methods on the Task object that allow you to see whether or not it’s been canceled, whether or not it’s finished, and to introspect as you go along in your code.
07:59
The Python threading library and the asyncio library solves similar problems. They’re both for concurrency in I/O-bound functions.
08:08
So why would you choose one over the other? Because asyncio concurrency is managed by Python and it doesn’t have as much overhead as the threads in an operating system.
08:19 It generally tends to outperform threads. That being said, as you’ve seen from the code, it’s a little more complicated to actually implement,
08:29
and as I mentioned earlier, asyncio is still new. Your favorite library might not support it yet. Changes are coming and it’s being implemented slowly across a lot of libraries, but your mileage may vary. If a lot of the coders that you’re working with are used to other languages besides Python, the threading library’s approach is probably more similar to what they’re accustomed.
08:51
Finally, threading is preemptive. asyncio is cooperative. If you’re in complete control of the code, this probably doesn’t matter all that much, but it’s an implementation detail that you may want to pay attention to because it could affect the design of your code.
09:07 Up until now, I’ve been showing you concurrency in I/O-bound situations that run on a single processor. In the next lesson, I’ll show you how to use multiple processes.
Chris Bailey RP Team on Nov. 10, 2025
@Alexandru-Rudi M. - Thanks for letting us know. The course was recently updated to include changes to the libraries and the captions needed some additional fixes. They have been updated.
Become a Member to join the conversation.

Alexandru-Rudi M. on Nov. 10, 2025
Hello Christopher,
The English subtitles stop being aligned with the audio starting at 02:29. From that point forward what is being said and what is being displayed is very different.
Kind regards
Rudi