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