Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does asyncio.sleep work with negative values?

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?

like image 354
user1475412 Avatar asked Sep 08 '16 18:09

user1475412


People also ask

What is the difference between await asyncio and asyncio sleep?

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.

Is aasyncio sleep blocking the execution of the task?

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.

How does asyncio work?

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.

How does asyncio cut down on wait time?

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.


1 Answers

Well, looking at the source:

  • delay == 0 is special-cased to return immediately, it doesn't even try to sleep.
  • Non-zero delay calls 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.
  • This is not defined in events.py, because it's different on Windows from on UNIX.
  • Either way, 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)
    
  • That one is also in 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.
  • Every time it checks, it strips out all cancelled scheduled things, then runs all remaining scheduled callbacks, in order, until it hits one that's not ready.

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.

like image 50
Vivian Avatar answered Sep 27 '22 18:09

Vivian