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

Talk Python's Mastodon Michael Kennedy's Mastodon