Python 3, an Illustrated Tour Transcripts
Chapter: Asynchronous Programming
Lecture: Cooperative Multitasking From the Ground Up
Login or
purchase this course
to watch this video and the rest of the course contents.
0:00
I'm going to go through some code here just to give you some insight into how you could make your own asynchronous library
0:09
and I didn't come up with this, this was from a presentation I saw from Robert Smallshire, and so I'll credit him on this, this isn't his code,
0:17
but this is adapted code from him, but I really like the approach here to give you some insight into developing an asynchronous framework.
0:25
Here, I've got a function called map, and if you're familiar with functional programming, map takes a function and a sequence
0:31
and it applies that function to every item in the sequence. So this should be pretty straightforward. We're creating a result list here
0:37
and we're just looping over our sequence and we're appending into that.
0:41
We could use a list comprehension or whatnot for this, but bare with me for a minute,
0:44
if we wanted to make this asynchronous, then we've got to change it a little bit it's got to yield, it's got to give something else the chance to run
0:52
and so I'm going to put a yield in here and I'm going to change it to say async map here.
0:57
So this looks very similar, I just put a yield in there and it says after I do some work, yield give something else the ability to run.
1:03
So if you're familiar with generators, you'll know that generators in Python allow you to call them
1:08
and once you call them and they get to this yield point they freeze their state and then when you call next on them,
1:14
when you're looping over a generator, you can resume the state exactly where it left off. So we're going to change this function into a generator
1:23
so that we can loop over it and we can call next on it. So here's an example of doing that here.
1:28
Our function is a generator and we're going to make an instance of this generator, we're going to pass in a lambda in there that just adds to something
1:35
and we're going to pass in range of 3 so 0 up not including 3 and if you're familiar with the iteration protocol, how for loops work under the covers,
1:44
basically you get an iterator and then you call next on that iterator. Well generator in Python is an iterator and so you can call next on a generator.
1:52
So I'm going to call next on it and that's going to say okay, I'm going to apply lambda to the first guy in range.
2:00
The first guy in range is zero the lambda adds 2 to zero, and at that point it's going to append that and it's going to yield
2:07
and so it yielded, it appended into our result that guy, the 2 and then we're going to call next again and it will put three in that result
2:17
and note that it's giving me back my interpreter here. I have my interpreter back, I'm doing this from the console here
2:23
and maybe I do some other work, I can say 5+7. Well, that's 12, and now I want to go back and I want to do this again.
2:30
So I call next again and it does some more work and it's going to stick 4 on the end, the iteration protocol when I call next again, I'm done,
2:37
It's going to give me a stop iteration but note that since I returned the result list that comes in my exception here.
2:44
So the stop iteration also has my result at the end of it. So that's pretty cool, so I can take that concept here and make a function from it now.
2:53
So I'm going to make a function called runner that takes a generator and it's just going to go into a while loop and it's going to call next repeatedly
2:59
until I get a stop iteration and when I get to stop iteration exception, I'm going to say well pull off the value guy
3:05
because that's going to be whatever the generator returned. And we can run that here and we can see that
3:10
when I pass in the same code here that I had previously I pass it into my runner function, I get that result out of it.
3:17
Pretty cool, but what this is allowing me to do is right after this next here, I could work on something else
3:23
or have multiple generators that I'm working on at the same time. So in order to do that, we're going to make a class called a task
3:31
a task will wrap a generator, we're just going to pass a generator into it and it will have an ID.
3:36
So we're going to just take an ID and keep track of our generator as instances in there. The ID is tracked as a class variable here.
3:45
Okay, so now we have a task, let's make a scheduler, a scheduler is going to take task and it's going to run them.
3:52
We're going to import from the collections module, let's call the deck or the double ended queue.
3:57
This allows us to efficiently stick things in the front and in the back and pull them off at either end very quickly.
4:03
What we're going to do is stick tasks into our deck pull them off the front and then stick them back into the other end as we're working on them.
4:10
So we're going to have a deck here with our tasks in it and we're going to make 2 other attributes, results and exceptions,
4:15
those are both dictionaries, they're going to map the task ID to either the result that came out of it or if there is an exception
4:22
they're going to map the task to the exception. And then we have an add method and add method takes a generator
4:27
and it just sticks it into our tasks list after it wraps it with the task class. Now, we have the run method,
4:34
this is where the meat of our scheduler is. It's just going to be very similar to what we saw before here.
4:39
We're going to have a while loop it's going to have an infinite loop. It's going to say if we don't have any tasks pop out of there.
4:45
Otherwise, what we're going to do is we're going to get our first task, our T from the left hand side and we're going to call next on it.
4:52
We're going to print out that we're running it, but we're going to call next on it. So it's going to do some work until it gets to that yield,
4:58
and it might have other things that happen, we might get a stop iteration. So we might be done with that generator.
5:03
If we did get a stop iteration that indicates that we're done and we're going to stick into our results dictionary whatever we got for the value there.
5:11
We might also get an exception, if some exception happen so we can just remember our exception.
5:16
If our generator is still running, so we didn't get a stop iteration or an exception, we're going to stick it back into the end of this deck.
5:22
So it's going to come back in the other end and then we're going to come back up to the top here and we're going to get our next task here
5:29
and we're going to work on that one, and we'll just keep working on these and they're all going to yield or they should yield
5:34
and allow other tasks to work at the same time. And at some point, all the tasks will be done, we'll break out of this.
5:40
So let's just look at an example of running this here. I'm going to make two generators async map g1 and another one async map
5:49
one is adding to the other, one has a lambda that is multiplying by 3 and they have slightly different input sizes,
5:56
we'll make an instance of our scheduler and we'll just add those two generators to them
6:00
and then we'll call run and we'll see that run is switching off between the two it's going to say I'm running 1 now, I'm running 2, now I'm running 1,
6:06
and at some point 1, which is only three long, gets finished and it says this is the result of running that
6:13
and it keeps working and now it's just working on 2, and then it gets the result of 2.
6:18
And finally it's done, we can say what are the results of the scheduler and it says well the results from task 1 are this
6:26
and the results of task 2 are that, but note that it interwove those results. It worked on both of them at the same time.
6:33
Hopefully, that gives you some insight into how you can yield and allow something else to run and then come back and work on something else.
6:41
So as long as we have these yields or awaits in Python 3.6 in our coroutines that we're creating, we can take advantage of this asyncio framework