---
title: 'Asyncio in Python - Full Tutorial'
source: 'https://youtube.com/watch?v=Qb9s3UiMSTA'
video_id: 'Qb9s3UiMSTA'
date: 2026-06-14
duration_sec: 1498
---

# Asyncio in Python - Full Tutorial

> Source: [Asyncio in Python - Full Tutorial](https://youtube.com/watch?v=Qb9s3UiMSTA)

## Summary

This video explains asynchronous programming in Python using asyncio, covering core concepts like the event loop, coroutines, tasks, futures, and synchronization primitives. It demonstrates how to write efficient concurrent code for I/O-bound tasks.

### Key Points

- **Synchronous vs Asynchronous Programming** [00:00] — Synchronous programming executes tasks sequentially, pausing at each step. Asynchronous programming allows starting multiple tasks without waiting for previous ones to finish, improving efficiency for I/O-bound operations.
- **When to Use Async IO vs Threads vs Processes** [00:50] — Async IO is best for many waiting tasks (e.g., network requests). Threads are for I/O-bound tasks that share data. Processes are for CPU-intensive tasks that run in parallel across cores.
- **The Event Loop** [02:02] — The event loop is the core that manages and distributes tasks. It runs tasks, pauses them when they await, and resumes them when the awaited operation completes, ensuring efficient utilization.
- **Coroutines and the Await Keyword** [03:29] — Coroutines are defined with `async def`. Calling a coroutine function returns a coroutine object, which must be awaited to execute. `asyncio.run()` starts the event loop and runs the main coroutine.
- **Await Keyword Usage** [06:21] — `await` is used inside async functions to pause execution until the awaited coroutine completes. Coroutines do not start executing until awaited.
- **Tasks for Concurrent Execution** [10:34] — Tasks schedule coroutines to run as soon as possible, allowing multiple coroutines to run concurrently. `asyncio.create_task()` creates a task. Tasks switch when one is idle (e.g., sleeping).
- **Gather Function** [13:41] — `asyncio.gather()` runs multiple coroutines concurrently and collects results in a list. It does not cancel other tasks if one fails.
- **Task Groups** [15:17] — `asyncio.TaskGroup` (Python 3.11+) provides better error handling: if one task fails, others are cancelled. Tasks are created with `tg.create_task()`.
- **Futures** [17:33] — A future is a promise of a future result. It is lower-level and typically used in libraries. You await the future to get the result, not necessarily the task.
- **Synchronization Primitives: Lock** [19:07] — Lock ensures only one coroutine accesses a shared resource at a time. Use `async with lock:` to acquire and release the lock.
- **Semaphore** [22:10] — Semaphore limits the number of coroutines that can access a resource concurrently. Useful for throttling, e.g., limiting concurrent network requests.
- **Event** [23:17] — Event is a simple boolean flag that allows coroutines to wait until it is set. Use `await event.wait()` and `event.set()`.

### Conclusion

Asyncio enables efficient concurrent I/O-bound code in Python. Understanding the event loop, coroutines, tasks, and synchronization primitives is essential for building responsive applications.

## Transcript

imagine programming is a journey from
point A to D in traditional synchronous
programming we travel in a straight line
stopping at each point before moving to
the next this means if there's a delay
at any point everything pauses until we
can move on now a synchronous
programming changes the game it allows
us to start tasks at b c and d even if
the task at a isn't finished yet this is
like sending out Scouts to explore
multiple paths at once without waiting
for the first Scout to return before
sending out the next this way our
program can handle multiple tasks
simultaneously making it more efficient
especially when dealing with operations
that have waiting times like loading a
web page and that's the essence of
asynchronous programming making our code
more efficient by doing multiple things
at once without the unnecessary waiting
so now let's quickly discuss when we
should use async iio because when we
build software choosing the right
concurrency model and picking between
asyn iio threads or processes is crucial
for performance and efficiency now async
iio is your choice for tasks that wait a
lot like Network requests or reading
files it excels in handling many tasks
concurrently without using much CPU
power this makes your application more
efficient and responsive when you're
waiting on a lot of different tasks now
threads are suited for tasks that may
need to wait but also share data they
can run in parallel within the same
application making them useful for tasks
that are IO bound but less CPU intensive
IO meaning input output now for CPU
heavy tasks processes are the way to go
each process operates independently
maximizing CPU usage by running in
parallel across multiple cores this is
ideal for intensive computations in
summary choose asyn iio for managing
many waiting tasks efficiently threads
for parallel tasks that share data with
minimal CPU use and processes for
maximizing performance on CPU intensive
tasks now that we know when to use async
iio let's dive into the five key
Concepts that we need to understand the
first concept is the event Loop in
Python's async iio The Event Loop is the
core that manages and distributes tasks
think of it as a central Hub with tasks
circling around it waiting for their
turn to be executed each task takes its
turn in the center where it's either
executed immediately or paused if it's
waiting for something like data from the
internet when a task awaits it steps
aside making room for another task to
run ensuring the loop is always
efficiently utilized once the awaited
operation is complete the task will
resume ensuring a smooth and responsive
program flow and that's how async io's
event Loop keeps your Python program
running efficiently handling multiple
tasks a synchronously so just a quick
pause here for any of you that are
serious about becoming software
developers if you want to be like Max
who landed a 70k per your job in Just 4
months of work consider checking out my
program with course careers now this
teaches you the fun fundamentals of
programming but also lets you pick a
specialization taught by an industry
expert in front end backend or devops
beyond that we even help you prepare
your resume we give you tips to optimize
your LinkedIn profile how to prepare for
interviews we really only succeed if our
students actually get jobs that's the
entire goal of the program so if that's
at all of interest to you we do have a
free introduction course that has a ton
of value no obligation no strings
attached you can check it out for free
from the link in the description so now
that we understand understand what the
event Loop is it's time to look at how
we create one and then talk about the
next important concept which is co-
routines now whenever we start writing
asynchronous code in Python We Begin by
importing the async io module now this
is built into python you don't need to
install it and for the purpose of this
video I'll be referencing all of the
features in Python version 3.11 and
above so if you're using an older
version of python just make sure you
update it because some things have
changed in the recent versions so we
begin by the module then we use the
command or the line async i.run and we
pass to this something known as a
co-routine function which will return a
co- routine object now asyn i.run is
going to start our event Loop and it's
going to start that by running a co-
routine now in our case there's two
types of co- routines we're concerned
with we have a co- routine function
which is this right here and we have
what's returned when you call a co-
routine function I know it seems a bit
strange but when you call Main like this
when it's defined using this async
keyword this returns to us something
known as a co- routine object now the
co-routine object is what we need to
pass here to async i.run it's going to
wait for that to finish and it's going
to start the event Loop for us where it
handles all of our asynchronous
programming so recap import the module
Define some asynchronous functions so
async and then you write the function
name out this is known as a co- routine
function you then call the function and
pass that to async i.run and that's
going to start your event Loop and allow
you to start running asynchronous code
that starts from this entry point now to
illustrate this a bit further let's look
at the difference between an
asynchronous function something defined
with this async keyword and a normal
function so watch what happens if I go
here and I simply call this function
some of you may assume that it's simply
going to print out start of main Cod
routine but you'll see that that's
actually not the case I know that my
terminal is a little bit messy here but
it says co-routine main was never
awaited now the reason we get that issue
is because when we call the function
here what we're actually doing is we're
generating a co-routine object this
co-routine object needs to be awaited in
order for us to actually get the result
of its execution now if we want to see
this even more visually we can actually
print out what we get when we call this
main function so let's call it here and
notice that we actually get this
co-routine object so when you call a
function defined with the async keyword
it returns a co-routine object and that
coroutine object needs to be awaited in
order for it to actually execute so
that's why we use the async i.run syntax
because this will handle awaiting this
Co routine and then allow us to write
some more asynchronous code now the next
thing that we need to look at is the
await keyword now the await keyword is
what we can use to await a coverou
tetine and to actually allow it to
execute and for us to get the result the
thing is though we can only use this
awake keyword inside of an asynchronous
function or inside of a code routine so
let's write another code routine and see
how we would await it and how we get its
result so now I've included a slightly
more complex example where we're
actually waiting on a different code
routine just to see how that works so
notice that we have a code routine up
here and what this is aiming to do is
simulate some input output bound
operation now that could be going to the
network and retrieving some data trying
to read file something that's not
controlled by our program that we're
going to wait on the result from so in
this case you can see that we fetch some
data we delay so we're just sleeping for
a certain amount of seconds just to
simulate that input output bound
operation we then get the data and we
return it now we know this is a co-
routine because we've defined it as an
asynchronous function now remember that
in order for a co- routine to actually
be executed it needs to be awaited now
in this case what we do is we create a
task and this task is the co-routine
object now the co-routine object at this
point in time is not yet being executed
and the reason it's not being executed
yet is because it hasn't been awaited
what I'm trying to show you is that when
you call an asynchronous function it
returns a co- routine that co- routine
needs to be awaited before it will
actually start executing so in this case
here we now await the task when we await
it it will start executing and we'll
wait for it to finish before we move on
to the rest of the code in our program
so let's run the code and see what the
output is here and you can see it says
start of main code routine data fetched
it then receives the results and it says
the end of the main code routine now
let's clear that and let's look at a
slightly different example so let's take
this result code right here and let me
just get rid of this
comment and let's put this actually at
the end of this function so now what we
have is print start of main co- routine
we create the co- routine object we then
print end of main routine object then we
await the code routine and I just want
to show you the difference in the result
that we're going to get so let's run the
code and notice we get start of main
code routine end of main code routine
and then we get fetching data data
fetched and then we get the result now
the reason we got this is because we
only created the code routine object
here we didn't yet await it so it wasn't
until we hit this line right here that
we waited for the execution of this to
finish before moving on to the next line
it's really important to understand that
fact that a code routine doesn't start
executing until it's awaited or until we
wrap it in something like a task which
we're going to look at later so I've
made a slight variation to the last
example and you can see what we're doing
now is we're creating two different code
routine objects and we're then awaiting
them now I want you to pause the video
and take a guess of what you think the
output's going to be and how long you
think it will take for this to execute
go ahead pause the video I'm going to
run the code now and explain what
happens so when I run this if if we move
it up here you'll see that we get
fetching data id1 data fetched id1 we
then receive the result and then we go
ahead and we fetch it for id2 now let's
clear this and run it one more time and
you can see that it takes 2 seconds we
fetch the first result it takes another
2 seconds and we fetch the second result
now this might seem counterintuitive
because you may have guessed that when
we created these two coroutine objects
they were going to start running
concurrently and that means that it
would only take us a total of 2 seconds
and we'd immediately get both of the
results but remember a code routine
doesn't start running until it's awaited
so in this case we actually wait for the
first co- routine to finish and only
once this has finished do we even start
executing the second co- routine meaning
that we haven't really got any
performance benefit here we've just
created a way to kind of wait for a task
to be finished that's all we've really
learned at this point in time now that
we understand this concept we can move
over and talk about tasks and see how we
can actually speed up an operation ation
like this and run both of these tasks or
these co- routines at the same time so
now we're moving on to the next
important concept which is a task now a
task is a way to schedule a co- routine
to run as soon as possible and to allow
us to run multiple co- routines
simultaneously now the issue we saw
previously is that we needed to wait for
one co- routine to finish before we
could start executing the next with a
task we don't have that issue and as
soon as a co- routine is sleeping or
it's waiting on something that's not in
control of our program we can move on
and start executing another task we're
never going to be executing these tasks
at the exact same time we're not using
multiple CPU cores but if one task isn't
doing something if it's idle if it's
blocked if it's waiting on something we
can switch over and start working on
another task the whole goal here is that
our program is optimizing its efficiency
so we're always attempting to do
something and when we're waiting on
something that's not in control of our
program we switch over to another task
and start working on that so here's a
quick example that shows you how we
would optimize kind of the previous
example that we looked at what we do
here is we use the simple create task
function now there's a few other ways to
make tasks which I'm going to show you
in a second but this is the simplest
what we do is we say task one is equal
to asyn io. create task and then we pass
in here a co-routine object it's a
co-routine object because this is a
co-routine function we call the function
and that returns to us a co- routine so
in this case we pass an ID then we pass
some time delay now if this was running
synchronously so if we had to wait for
each of these tasks to run it would take
us 2 seconds plus 3 seconds plus 1
second so a total of 6 seconds for this
code to execute however you'll see now
that what will happen is we'll be able
to execute this code in simply 3 seconds
because as soon as one of the tasks is
idle and we're waiting on this sleep we
can go and execute or start another task
now what I do is I still need to await
these tasks to finish so I just await
them all in line here and then collect
all of their different results so let's
bring the terminal up and let's run this
code and make sure it works and notice
that it starts all three Co routines
pretty much immediately and then we get
all of the data back at once in about 3
seconds again that differs from if we
were to use just the normal C routines
and we didn't create a task we'd have to
wait for each of them to finish before
we can move on to the next one so as a
quick recap when we create a task we're
essentially scheduling a code routine to
run as quickly as possible possible and
we're allowing multiple Co routines to
run at the same time as soon as one co-
routine isn't doing something and it's
waiting on some operation we can switch
to another one and start executing that
now all of that is handled by the event
loop it's not something we need to
manually take care of however if we do
want to wait on one task to finish
before moving to the next one we can use
the await syntax so it would be possible
for me to go here and write some code
like this and now we would see if we
execute the code and we can go ahead and
do that that we'll start the first and
the second code routine but we won't
start the third one until the first and
the second one are done so using a
synchronous programming gives us that
control and allows us to synchronize our
code in whatever manner we see fit so
now we move on to a quick example where
I'm going to show you something known as
The Gather function Now The Gather
function is a quick way to concurrently
run multiple co- routines just like we
did manually before so rather than
creating a task for every single one of
the co- routines using that create task
function we can simply use gather and it
will automatically run these
concurrently for us and collect the
results in a list the way it works is
that we pass multiple code routines in
here as arguments these are
automatically going to be scheduled to
run concurrently so we don't need to
wait for them to finish before we start
executing the next one and then we will
gather all of the results in a list in
the order in which we provided the co-
routines so the result of this one will
be the first element in the list second
element in the list third element in the
list Etc and it's going to wait for all
of them to finish when we use this await
keyword which just simplifies this
process for us that then allows us to
have all of the results in one place so
we can parse through them using this for
Loop so let's go ahead and run this code
and you see that it starts all three of
our Co routines we wait 3 seconds and
then we get all of our different results
now one thing you should know about
gather is that it's not that great at
error handling and it's not going to
automatically cancel other co- routines
if one of them were to fail now the
reason I'm bringing that up is because
the next example I show you does
actually provide some built-in error
handling which means it's typically
preferred over gather but it's just
worth noting that if there is an error
that occurs in one of these different
code routines it won't cancel the other
code routines which means you could get
some weird state in your application if
you're not manually handling the
different exceptions and errors that
could occur so now we're moving on to
the last example in the topic of tasks
where we're talking about something
relatively new known as a task group now
this is a slightly more preferred way to
actually create multiple tasks and to
organize them together and the reason
for this is this provides some built-in
error handling and if any of the tasks
inside of our task groups were to fail
it will automatically cancel all of the
other tasks which is typically
preferable when we are dealing with some
Advanced errors or some larger
applications where we want to be a bit
more robust now the fetch data function
has not changed at all all we've done
here is we've started using async i.ask
group now notice that what I'm using
here is the async width now this is
what's known as an asynchronous context
manager you don't to understand that you
don't have to have seen context managers
before but what this does is give us
access to this TG variable so we create
a task group as TG and now to create a
task we can say TG our task group.
create task just like we did before in
that first example we can create an
individual task we can then add this to
something like our tasks list if we care
about the result of it and now once we
get by this asynchronous width so once
we get down here to where I have the
comment what happens is all of these
tasks will have already been executed so
the idea is this is a little bit cleaner
it's automatically going to execute all
of the tasks that we add inside of the
task group once all of those tasks have
finished then this will stop blocking
when I say stop blocking that means we
can move down to the next line of code
and at this point we can retrieve all of
the different results from our tasks now
there's various different ways to go
about writing this type of code but the
idea is you simply create a task here as
soon as it's created inside of the task
group we now need to wait for that and
all the other tasks to finish before we
unblock from this block of code then
once they're all finished we move on to
the next lines of code now similarly to
any other task that we looked at before
these are all going to run concurrently
meaning if one task is sleeping we can
go on and we can start another task and
work on something else so those are
tasks obviously there's a lot more you
can do here but understand that you run
tasks when you want to execute code
concurrently and you want multiple
different operations to be happening at
the same time so now we're moving on to
the fourth important concept which is a
future now it's worth noting that a
future is not something that you're
expected to write on your own it's
typically utilized in lower level
libraries but it's good to just be
familiar with the concept in case you
see it in asynchronous programming so
I'll go through this fairly quickly but
really what a future is is a promise of
a future result so all it's saying is
that a result is to come in the future
you don't know exactly when that's going
to be that's all future is so in this
case you can see that we actually create
a future and we await its value what we
do is we actually get the event Loop you
don't need to do this you'll probably
never write this type of code we create
our own future we then have a new task
that we create using async iio and you
can see the task is set future result
inside here we wait for 2 seconds so
this is some blocking operation and then
we set the result of the future and we
print out the result here we AIT the
future and then we print the result now
notice we didn't actually await the task
to finish we awaited the future object
so inside of the task we set the value
of the future and we awaited that which
means as soon as we get the value of the
future this task may or may not actually
be complete so this is slightly
different than using a task when we use
a future we're just waiting for some
value to be available we're not waiting
for an entire task or an entire co-
routine to finish that's all I really
want to show you here I don't want to
get into too many details that's a
future really just a promise of an
eventual result so now we're moving on
and talking about synchronization
Primitives now these are tools that
allow us to synchronize the execution of
various co- routines especially when we
have larger more complicated programs
now let's look at this example so we can
understand how we use the first
synchronization tool which is lock let's
say that we have some shared resource
maybe this is a database maybe it's a
table maybe it's a file doesn't matter
what it is but the idea is that it might
take a fair amount of time for us to
actually modify or do some operation on
this shared resource and we want to make
sure that no two co-routines are working
on this at the same time the reason for
that is if two co-routines were say
modifying the same file if they're
writing something to the database we
could get some kind of error where we
get a mutated state or just weird
results end up occurring because we have
kind of different operations happening
at different times and they're
simultaneously occurring when we want
really wait for one entire operation to
finish before the next one completes
that might seem a little bit confusing
but the idea is we have something and we
want to lock it off and only be using it
from one co- routine at a time so what
we can do for that is we can create a
lock now when we create a lock we have
the ability to acquire the lock and we
do that with this code right here which
is async with lock now this again is an
asynchronous context manager and what
this will do is it will check if any
other code routine is currently using
the lock if it is it's going to wait
until that code routine is finished if
it's not it's going to go into this
block of code now the idea is whatever
we put inside of this context manager
needs to finish executing before the
lock will be released which means we can
do some critical part of modification we
can have some kind of code occurring in
here that we know will happen all at
once before we move on to a different
task or to a different code routine the
reason that's important is because we
have something like an await maybe we're
waiting a network operation to save
something else that could trigger a
different task to start running in this
case we're saying hey within this lock
wait for all of this to finish before we
release the lock which means that even
though another task could potentially be
executing when the Sleep occurs it can't
start executing this critical part of
code until all of this is finished and
the lock is released so all the lock is
really doing is it's synchronizing our
different co- routines so that they
can't be using this block of code or
executing this block of code while
another code routine is executing it
that's all it's doing it's locking off
access to in this case a critical
resource that we only want to be
accessed one at a time so in this case
you can see that we create five
different instances of this Co routine
we then are accessing the lock and then
again once we get down here we're going
to release it so if we bring up the
terminal here and we start executing
this you'll see that we have resource
before modification resource after
before after before after and the idea
is even though that we've executed these
cortines concurrently we're gating them
off and we're locking their access to
this resers so that only one can be
accessing it at a time moving on the
next synchronization primitive to cover
is known as the semaphore now a
semaphore is something that works very
similarly to a lock however it allows
multiple Cod routines to have access to
the same object at the same time but we
can decide how many we want that to be
so in this case we create a semaphore
and we give give it a limit of two that
means only two co- routine story can
access some resource at the exact same
time and the reason we would do that is
to make sure that we kind of throttle
our program and we don't overload some
kind of resource so it's possible that
we're going to send a bunch of different
network requests we can do a few of them
at the same time but we can't do maybe a
thousand or 10,000 at the same time so
in that case we would create a semaphor
we'd say okay our limit is maybe five at
a time and this way now we have the
event Loop automatically handled this
throttle our code intentionally to only
send maximum five requests at a time
anyways let's bring up our terminal here
and run this code so Python 3 semap 4.
piy and you can see that we can access
the resource kind of two at a time and
modify it but we can't have any more
than that now moving on to the last
primitive we're going to talk about this
is the event now the event is something
that's a little bit more basic and
allows us to do some simpler
synchronization in this case we can
create an event and what we can do is we
can await the event to be set and we can
set the event and this acts as a simple
Boolean flag and it allows us to block
other areas of our code until we've set
this flag to be true so it's really just
like setting a variable to true or false
in this case it's just doing it in the
asynchronous way so you can see we have
some Setter function maybe it takes two
seconds to be able to set some result we
then set the result and as soon as that
result has been set we can come up here
we await that so we wait for this to
finish and and then we can go ahead and
print the event has been set continue
execution so we can bring this up here
and quickly have a look at this so
Python 3 if we spell that correctly
event. pi and you'll see it says
awaiting the event to be set event has
been set event has been set continuing
the execution okay pretty
straightforward it's just a Boolean flag
that allows us to wait at certain points
in our program there's lots of different
times when you would want to use this
but I just wanted to quickly show you
that we do have something like this that
exists now there is another type of
primitive here that's a bit more
complicated called the condition I'm not
going to get into that in this video in
fact I'm going to leave the video here
if you guys enjoyed this make sure you
leave a like subscribe to the channel
and consider checking out my premium
software development course with course
careers if you enjoy this teaching style
and you're serious about becoming a
developer anyways I will see you guys in
another YouTube
[Music]
video
