asyncio.sleep 如何处理负值?

How does asyncio.sleep work with negative values?

我决定使用 Python 的 asyncio 实现睡眠排序 (https://rosettacode.org/wiki/Sorting_algorithms/Sleep_sort),当时我有一个奇怪的发现:它适用于负值(和 returns立即与 0)!

这是代码(你可以 运行 这里 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))

正如预期的那样,代码执行需要 5 秒,但结果始终是 [0, -5, -4, -3, -2, -1, 1, 2, 3, 4, 5]。我可以理解 0 立即返回,但是负值如何以正确的顺序返回?

如果您查看 asyncio 源代码,sleep special cases 0 和 returns 立即。

if delay == 0:
    yield
    return result

如果您继续浏览源代码,您会看到任何其他值都被传递到事件循环的 call_later 方法。查看 call_later 是如何为默认循环 (BaseEventLoop) 实现的,您会看到 call_later passes a time to call_at.

self.call_at(self.time() + delay, callback, *args)

值按顺序翻转的原因是使用负延迟创建的时间出现在使用正延迟的时间之前。

嗯,看看 source:

  • delay == 0 是 return 的特殊情况,它甚至不会尝试休眠。
  • 非零延迟调用 events.get_event_loop()。由于 asyncio.tasks 中没有对 events.set_event_loop_policy(policy) 的调用,它似乎会退回到默认设置,除非它已经在其他地方设置,并且 the default is asyncio.DefaultEventLoopPolicy.
  • 这在 events.py 中没有定义,因为它在 Windows from on UNIX 上有所不同。
  • 无论如何,sleep 调用 loop.create_future()。在 base_events.BaseEventLoop 中定义了一些继承。这只是对 Future() 构造函数的简单调用,没有重要的逻辑。
  • 它从 Future 的实例委托回循环,如下所示:

    future._loop.call_later(delay,
                            futures._set_result_unless_cancelled,
                            future, result)
    
  • 那个也在BaseEventLoop中,但仍然没有直接处理delay号码:它调用self.call_at,将当前时间添加到延迟中。
  • call_at 计划和 return 一个 events.TimerHandle,回调是告诉 Future 它已经完成。 return 值仅在要取消任务时才相关,它会自动在结束时进行清理。调度是重要的一点。
  • _scheduled 通过 heapq 排序 - 一切都按排序顺序进行,计时器按其 _when 排序。这是关键。
  • 每次检查时,它都会删除所有已取消的预定事件,然后 运行 按顺序删除所有剩余的预定回调,直到遇到未准备好的回调。

TL;DR:

asyncio 一起睡负持续时间将任务安排在过去 "ready"。这意味着它们会到达计划任务列表的顶部,并且一旦事件循环检查就 运行 。实际上,0 排在第一位,因为它甚至不进行调度,但其他所有内容都会以 "running late" 的形式注册到调度程序,并按照迟到的顺序立即处理。