I decided to implement sleep sort (https://rosettacode.org/wiki/Sorting_algorithms/Sleep_sort) using Python's asyncio
when I made a strange discovery: it works with negative values (and returns immediately with 0)!
Here is the code (you can run it here https://repl.it/DYTZ):
import asyncio
import random
async def sleepy(value):
return await asyncio.sleep(value, result=value)
async def main(input_values):
result = []
for sleeper in asyncio.as_completed(map(sleepy, input_values)):
result.append(await sleeper)
print(result)
if __name__ == '__main__':
loop = asyncio.get_event_loop()
input_values = list(range(-5, 6))
random.shuffle(input_values)
loop.run_until_complete(main(input_values))
The code takes 5 seconds to execute, as expected, but the result is always [0, -5, -4, -3, -2, -1, 1, 2, 3, 4, 5]
. I can understand 0 returning immediately, but how are the negative values coming back in the right order?
The difference here is that await asyncio.sleep will not block other async tasks, if you have any running. But I am confused because reading some documentation, this example was given.But this does not happen with my code.
await asyncio.sleep () is blocking my execution code. Yes, it's blocking the execution of the task. [i async for i in async_generator ()] is going to await everything from the generator, and the generator only yields values after the aasyncio.sleep completes.
Data is being read from the buffer and returned to our humble user. In summary, asyncio uses generator capabilities, that allow pausing and resuming functions. It uses yield from capabilities that allow passing data back and forth from the inner-most generator to the outer-most.
Lastly, let's have an example of how asyncio cuts down on wait time: given a coroutine generate_random_int () that keeps producing random integers in the range [0, 10], until one of them exceeds a threshold, you want to let multiple calls of this coroutine not need to wait for each other to complete in succession.
Well, looking at the source:
delay == 0
is special-cased to return immediately, it doesn't even try to sleep.events.get_event_loop()
. Since there are no calls to events.set_event_loop_policy(policy)
in asyncio.tasks
, it would seem to fall back on the default unless it's already been set somewhere else, and the default is asyncio.DefaultEventLoopPolicy
.events.py
, because it's different on Windows from on UNIX.sleep
calls loop.create_future()
. That's defined a few inheritances back, over in base_events.BaseEventLoop
. It's just a simple call to the Future()
constructor, no significant logic.From the instance of Future
it delegates back to the loop, as follows:
future._loop.call_later(delay,
futures._set_result_unless_cancelled,
future, result)
BaseEventLoop
, and still doesn't directly handle the delay
number: it calls self.call_at
, adding the current time to the delay.call_at
schedules and returns an events.TimerHandle
, and the callback is to tell the Future
it's done. The return value is only relevant if the task is to be cancelled, which it is automatically at the end for cleanup. The scheduling is the important bit._scheduled
is sorted via heapq
- everything goes on there in sorted order, and timers sort by their _when
. This is key.TL;DR:
Sleeping with asyncio
for a negative duration schedules tasks to be "ready" in the past. This means that they go to the top of the list of scheduled tasks, and are run as soon as the event loop checks. Effectively, 0 comes first because it doesn't even schedule, but everything else registers to the scheduler as "running late" and is handled immediately in order of how late it is.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With