Python asyncio wait_for 同步化

3

使用 Python 3.6.8 版本

async def sleeper():
    time.sleep(2)

async def asyncio_sleeper():
    await asyncio.sleep(2)

await asyncio.wait_for(sleeper(), 1)

await asyncio.wait_for(asyncio_sleeper(), 1)

使用time.sleep不会超时,而asyncio.sleep会超时。

我的直觉是,在协程上调用wait_for会基于协程执行的时间来计算超时时间,而不是基于协程中的单个异步调用。

背后发生了什么导致这种行为,并且有没有一种方法可以修改行为以符合我的直觉?

1个回答

6
最简单的答案是asyncio基于协作式多任务处理,而time.sleep不协作。time.sleep(2)会阻塞线程两秒钟,包括事件循环在内,没有人能够解决这个问题。另一方面,asyncio.sleep经过精心编写,当您await asyncio.sleep(2)时,它会立即暂停当前任务,并与事件循环安排好在2秒后恢复它。Asyncio的“睡眠”是隐式的,这允许事件循环在协程挂起时继续进行其他任务。相同的挂起系统允许wait_for取消任务,事件循环通过在await中“恢复”它来完成这个操作,其中它被挂起的await引发异常。
一般来说,一个协程没有等待任何东西是错误的写法,仅仅是名义上的协程。等待是协程存在的原因,而 sleeper 没有包含任何等待。

有没有办法修改行为以匹配我的直觉?

如果必须从 asyncio 调用旧的阻塞代码,请使用 run_in_executor。您将需要告诉 asyncio 何时这样做,并允许其执行实际的阻塞调用,如下所示:
async def sleeper():
    loop = asyncio.get_event_loop()
    await loop.run_in_executor(None, time.sleep, 2)
time.sleep(或其他阻塞函数)将被移交给单独的线程,并且sleeper将被暂停,直到time.sleep完成。与asyncio.sleep()不同的是,阻塞的time.sleep(2)仍将被调用并阻塞其线程2秒钟,但这不会影响事件循环,事件循环将继续执行,就像使用await asyncio.sleep()时一样。
请注意,取消等待run_in_executor的协程只会取消等待在另一个线程中完成阻塞的time.sleep(2)。阻塞调用将继续执行直到完成,这是预期的,因为没有通用机制来中断它。

1
现在你应该使用 asyncio.to_thread。 - Hacker

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接