Python asyncio 等待同步
使用的是 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 个回答
背后发生了什么,导致了这种行为?
最简单的解释是,asyncio 是基于一种叫做 合作式 多任务处理的方式,而 time.sleep
并不配合。调用 time.sleep(2)
会让当前的线程停顿两秒钟,这期间事件循环也会被阻塞,没人能对此做什么。
另一方面,asyncio.sleep
是经过精心设计的,当你使用 await asyncio.sleep(2)
时,它会立即暂停当前的任务,并安排事件循环在两秒后恢复这个任务。asyncio 的“睡眠”是隐式的,这样事件循环就可以在协程暂停的同时继续处理其他任务。这个暂停机制也允许 wait_for
取消任务,事件循环通过在暂停的地方“恢复”任务来实现,这样就会抛出一个异常。
一般来说,如果一个协程没有等待任何东西,那就很可能是写得不对,实际上只是个名义上的协程。等待是协程存在的原因,而 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)
仍然会被调用,并会让它的线程阻塞两秒,但这不会影响事件循环,事件循环会像使用 await asyncio.sleep()
时那样继续进行。
需要注意的是,取消一个等待 run_in_executor
的协程只会取消对阻塞的 time.sleep(2)
在另一个线程中完成的等待。阻塞调用会继续执行直到完成,这是可以预期的,因为没有通用的机制可以中断它。