Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

This lesson is for members only. Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

Hint: You can adjust the default video playback speed in your account settings.
Hint: You can set your subtitle preferences in your account settings.
Sorry! Looks like there’s an issue with video playback 🙁 This might be due to a temporary outage or because of a configuration issue with your browser. Please refer to our video player troubleshooting guide for assistance.

The asyncio Event Loop

In this lesson, you’ll learn about the Event Loop. You’ll see what it is and how to define one. The Event Loop is used to:

  • perform IO tasks
  • run asynchronous tasks
  • run callbacks

In addition to learning about what it is, you’ll see how to change a normal main() function into an event loop. You’ll see how to create numerous coroutines using Future objects and gather(). In the last section of the lesson, you’ll see an example demonstrating how using an asynchronous approach can make your code run faster.

00:00 Now, if I try and run this,

00:05 you can see I got another error message. It says coroutine 'main' was never awaited. What’s going on here? Well, what needs to happen is I need to actually create what’s called an event loop, and the event loop is what’s going to actually run these coroutines.

00:21 So you’re probably asking yourself “What’s an event loop?” Well, the event loop is the core of every async IO application. And what do event loops do? Well, they run asynchronous tasks, like this—they run these things. They run callbacks.

00:39 They perform network operations, IO operations, and other subprocesses. So you can basically think of the event loop as something that runs these callbacks, okay? And any IO associated with them, all right? Great.

00:55 So I need an event loop, and the event loop is going to allow us to run this asynchronous task right here, this coroutine. But how do I actually create the event loop?

01:08 What we’re going to do is we’re going to come down here into "__main__" and I’m going to say asyncio.run(main()).

01:24 And this little bit of code right here creates the event loop, and it says, “Okay, now that you have an event loop, I want you to run any task associated with main() inside this event loop.” Okay? All right, so let’s see what happens now.

01:39 Let’s come back here and rerun it,

01:45 and it looks like we got an actual response. The random number we got was 6. Now, you’re probably looking at this and you’re going “Well, was there really any difference? I mean, this still took roughly 3 seconds.” It’s like, what was the benefit of all that talk and all this?

02:03 So, I think the best way to do this is by running some timings. What I’m going to do is I’m going to say, start, the start time, is equal to time.I’m going to create a perf_counter(). Okay.

02:15 This will give me a start time and then we’ll have a stop time, and then we’ll look at the difference between the two. Okay? I’m going to do that.

02:25 And I’m going to create an elapsed time,

02:32 which is time.perf_counter(), and we’ll just subtract the start time from that. Okay. And then let’s print out the r value that I just got back, the random value that I just got,

02:53 and how long it took. We’ll say, f'{r} took {} seconds', like that. And what is this? It’s the elapsed, and then we’ll round it to just to 2 decimal points.

03:11 Okay, so it’s going to start a timer, it’s going to do this, it’s going to wait. So this await, what it’s going to do is it’s not going to run these two lines of code, 23 and 24, until 22 is finished, okay?

03:25 So let’s see how long that takes. And it will probably take around 3 seconds, but let’s just see.

03:34 And indeed, it took 3.00 seconds exactly. So that was great. Now let’s do this again, but let’s do it more than just one time. So I’m basically just going to copy this code,

03:53 paste it right here.

03:58 And now I want to do this a lot of times. I want to call randn() a lot of times, or numerous times, and so a way you can do that is you could await this thing, asyncio.gather(),

04:18 and in this I can create numerous randn() coroutines. Let me show you an example. I could say randn(), randn(), like that. So there was two of them, maybe let’s do one more.

04:40 I created three of these things and then the response is going to go into r—you could also think of r as random—and yeah.

04:46 So we’re going to do another start, we’re going to do this, and then we’re going to do a stop, and then we’re going to print this out. Now, how long do you think this is going to take? You may think, “Well, it took 3 seconds to do this one up here—there’s three of them—so it’ll probably take 3 * 3 is 9.

05:02 And that’s how long the synchronous version took, right? It took 9 seconds. Let’s see how long the async version takes.

05:13 So the first one, it got a 6, I took 3.00 seconds. And the second one, [1, 5, 1], and it also took 3.00 seconds. So, hm, that’s pretty interesting, right?

05:22 I did essentially three times the work and the amount of time it took was still 3 seconds. Now I could do this even more. Instead of doing this, let’s create a list comprehension, and I could say randn() for _ in range(10). So, 10 of these,

05:53 and they are 3 seconds each, so you would think, “Well, it would probably take about 30 seconds,” right? If this was a synchronous version, you would think it would take about 30 seconds. So I’m going to do 10 of these, but I need to unpack this thing, so I’m going to do star (*) and actually I’m just going to do the round braces here.

06:16 You’re probably wondering, “What does that do?” The square braces makes it a list comprehension. The round braces—you’re probably thinking the round braces makes it a tuple. In this case here, it actually doesn’t make it a tuple. It actually turns it into a generator, which is one of those things we created initially, right? Up here.

06:35 The odds() was a generator. And so, yeah, when you do round braces it actually produces a generator, and the * causes the generator to run, and then I’m unpacking those results.

06:45 So basically, I’m creating 10 of these. I’m creating 10 randn() coroutines and they’re all going to run, okay? So let’s see what happens. So, will this take 3 seconds or—like, if this was the synchronous version, it would take 30 seconds, but let’s see how long it takes.

07:08 So here it is, the code’s running. I got a 5, it took 3.00 seconds long, and now I’m going to do 10. Look, there’s 10 values there, and it took 3.01 seconds. So, once again, you can see this asyncio thing is actually doing something pretty interesting.

07:21 It’s actually running all this stuff—you could think of it as if it’s running it in parallel, because it only took 3 seconds. Now, it’s not technically running it in parallel.

07:30 What’s happening is it’s actually only running on one thread, so there’s a process—a Python process. Inside that Python process, there’s one thread, and this is running on that one thread.

07:42 But what’s happening is it’s telling the system—i.e. the computer—to sleep, and so the computer is handling the sleeping. And then once it’s finished, the computer comes back—like, the operating system comes back—and says, “Hey, Python, I’m done.” And it says, “I’m done,” on all these 10 tasks at basically the same amount of time, and then Python processes those.

08:03 So the sleeping doesn’t really happen in Python—it happens outside of Python inside the operating system. So, something that could have taken 30 seconds instead only took 3 seconds because it was happening in the background. Python really wasn’t taking care of that.

08:21 So, we went from this synchronous version to the asynchronous version. This all looks pretty good. The next thing that I want to do is I want to show you how to take these generators and these coroutines, and put them together and make what’s called an asynchronous generator.

08:39 Okay, so that’s next.

dgoodson12722 on March 3, 2020

You are an awesome instructor with the gift of making asyncio seem easy! Please, do more videos like this, please!

Angad on Aug. 6, 2020

Awesome instructor . Thank you . Please post more lessons on realpython.

himanshudhar6 on Feb. 15, 2021

Explained in a very confusing manner

Luis on March 7, 2021

I feel like the course is missing an explanation on what callbacks are.

Bartosz Zaczyński RP Team on March 8, 2021

@Luis Whenever you find an unfamiliar term, you can try looking it up in the Python glossary, which usually has a brief answer:

callback

A subroutine function which is passed as an argument to be executed at some point in the future.

For an in-depth explanation, you can usually find a Wikipedia article that goes into much more detail.

Let me know if you have any specific questions. I’ll be happy to help!

vijay singh on April 14, 2023

Is the code to this topic intentionally not made available in the ‘supporting materials’ section?

Martin Breuss RP Team on April 14, 2023

@vijay singh thanks for noting this! It looks like the original authors didn’t provide the code materials, we’ll work on fetching them from the associated tutorial and adding them here as a ZIP file as well.

In the meantime, you could copy the code you’re looking for from the source tutorial.

Become a Member to join the conversation.