首页 > 解决方案 > `asyncio.sleep(delay)` 是否保证睡眠至少 `delay` 秒?

问题描述

asyncio.sleep()的阻塞表亲time.sleep()不能保证它会在请求的时间内休眠。

实际的挂起时间可能少于请求的时间,因为任何捕获的信号都会在执行该信号的捕获例程后终止 sleep()。

asyncio.sleep()文档没有提到类似的限制。

asyncio.sleep()能够对睡眠时间做出更有力的保证吗?

标签: pythonpython-asyncio

解决方案


我不会说什么asyncio 保证,但根据实现,asyncio.sleep()(基本上,call_later())睡眠指定的时间间隔,但至少不准确等于实现中使用的系统时钟的分辨率。

让我们弄清楚。首先,asyncio使用单调时钟,在不同平台上具有不同的分辨率(Python 和 OS 分辨率)。例如,Windows这与15ms.

在保证方面,请注意对函数的注释BaseEventLoop.time

    def time(self):
        """Return the time according to the event loop's clock.
        This is a float expressed in seconds since an epoch, but the
        epoch, precision, accuracy and drift are unspecified and may
        differ per event loop.
        """
        return time.monotonic()

现在让我们看一下负责启动定时定时器的asyncio事件循环源码

        # Handle 'later' callbacks that are ready.
        end_time = self.time() + self._clock_resolution
        while self._scheduled:
            handle = self._scheduled[0]
            if handle._when >= end_time:
                break
            handle = heapq.heappop(self._scheduled)
            handle._scheduled = False
            self._ready.append(handle)

end_time = self.time() + self._clock_resolution显示回调可能比计划的更早触发,但在时钟分辨率内。Yuri Selivanov在这里明确说明了这一点:

在我看来,目前我们正在窥视未来的时间。我们为什么不做

       end_time = self.time() - self._clock_resolution

保证超时总是在请求时间之后触发,而不是之前?如果我们这样做,我看不出性能会如何变得更糟。

真的,让我们运行下一个程序(Windows 10 上的 Python 3.8):

import asyncio 
import time

async def main():
    print("Timer resolution", time.get_clock_info('monotonic').resolution)
    while True:
        asyncio.create_task(asyncio.sleep(1))

        t0 = time.monotonic()
        await asyncio.sleep(0.1)
        t1 = time.monotonic()

        print(t1 - t0)
asyncio.run(main()) 

我们看到上述行为:

Timer resolution 0.015625
0.09299999987706542
0.0940000000409782
0.0940000000409782
0.10900000017136335
...

但是在正文的开头,我至少说了时钟分辨率,因为asyncio在协作多任务处理的条件下工作,并且如果有一个贪婪的协程(或许多不那么贪婪的协程)不会太频繁地控制事件循环,我们有以下图片:

import asyncio 
import time

async def calc():
    while True:
        k = 0
        for i in range(1*10**6):        
            k += i
        await asyncio.sleep(0.1)  # yield to event loop
    

async def main():
    asyncio.create_task(calc())  # start greedy coroutine
    print("Timer resolution", time.get_clock_info('monotonic').resolution)
    while True:
        asyncio.create_task(asyncio.sleep(1))

        t0 = time.monotonic()
        await asyncio.sleep(0.1)
        t1 = time.monotonic()

        print(t1 - t0)
asyncio.run(main()) 

不出所料,这种情况正在向增加延迟的方向发生变化:

0.17200000025331974
0.1559999999590218
0.14100000029429793
0.2190000000409782

推荐阅读