Python asyncio 等待同步

3 投票
1 回答
4209 浏览
提问于 2025-06-18 03:58

使用的是 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) 时,它会立即暂停当前的任务,并安排事件循环在两秒后恢复这个任务。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) 在另一个线程中完成的等待。阻塞调用会继续执行直到完成,这是可以预期的,因为没有通用的机制可以中断它。

撰写回答