Reputation: 117681
I've looked all over the documentation, but could not find it.
Suppose multiple await
calls are done, and their coroutines are all ready to be resumed, what algorithm does asyncio
's event loop use to decide which coroutine gets resumed?
Upvotes: 2
Views: 604
Reputation: 1606
TLDR in python 3.10 the coroutine that was ready to be resumed the longest will continue if you are using the default event loop. However timers (and possibly io events) will be only checked once per execution of the event loop.
Internally pythons BaseEventLoop
keeps a deque self._ready
containing all the handles that are ready to get called.
A handle isn't quite a coroutine but related to it. Indeed asyncio.run wraps a coroutine in a task, and a task basically splits up a coroutine up into steps, where a single step is everything that needs to get executed to get to the next point in the coroutine where control is handed back to the event loop (this might be later then the next await statement since not all await statements yield control back to the event loop). A handle is just asyncio's internal way of referencing to a step that needs to be executed.
Handles are being executed in the same order that they have been put on the _ready
deque. This order is system dependent your system and is the following on Unix per iteration of the event loop:
1st. All non-io, non-timer based handles that became ready since the previous iteration of the event loop in the order these became ready.
2nd. All io based handles in the order they are returned by the Unix select system call since the previous iteration they became ready.
3th. All timer based handles whose timer has run out in order.
I didn't check but I believe on Windows when the proactor based event loop is used, you will get that the 1st and 2nd point above will be interleaved in the order they were ready.
The detailed answer already given by Mikhail Gerasimov explains how to find out what is done by the standard event loop in any given python version.
Upvotes: 0
Reputation: 39536
1) Documentation shows us that event loop starts with run_forever
/ run_until_complete
. We need to find source code of function(s) to see what happens next.
2) The Fastest way (I know) to do it is to search relevant name on github. Go to github.com/python/cpython, use search form on top-left:
Github will show you all occurrences in project. Make sure you search in code:
3) We need implementation, there are two of them found: one inside ProactorEventLoop where nothing interesting happens since it partly reimplement parent event loop; and one inside BaseEventLoop and it seems to be what we search for.
4) Let's examine the code. Pretty soon we'll see that everything comes to calling _run_once
inside while True
loop:
try:
events._set_running_loop(self)
while True:
self._run_once()
if self._stopping:
break
5) Let's go to _run_once
(we can just search def _run_once
on the page). Here's where scheduled things get resumed. There're two interesting places you'll see going through source code:
event_list = self._selector.select(timeout)
- it is where asyncio sleeps until activity on sockets. You can investigate this mechanism further reading documentation. But this place doesn't resume coroutines itself.This is the only place where callbacks are actually *called*. All other places just add them to ready.
Exactly what we searched for. You can read source code there (and inside _run_once
function in general) to see how callbacks are executed and how they are added to self._ready
to be executed. Callbacks includes ones added directly and ones asyncio uses internally to resume coroutines.
You can also reimplement event loop to play with it. Take a look at code here, it contains example of event loop reimplementing.
Few notes:
BaseEventLoop
, other event loops like custom uvloop may do something different.Upvotes: 3