Python 异步 wait_for 同步
Python asyncio wait_for synchronous
使用 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 会根据协程花费多长时间来超时,而不是根据协程中的各个异步调用。
导致这种行为的幕后发生了什么,是否有办法修改行为以符合我的直觉?
What is going on behind the scenes that results in this behavior
最简单的答案是asyncio是基于合作多任务处理,time.sleep
不合作。 time.sleep(2)
阻塞线程两秒,事件循环等等,任何人都无能为力。
另一方面,asyncio.sleep
很小心written,所以当你await asyncio.sleep(2)
时,它会立即挂起当前任务并安排事件循环在2秒后恢复它. Asyncio 的“休眠”是隐式的,它允许事件循环在协程挂起时继续执行其他任务。相同的挂起系统允许 wait_for
取消任务,事件循环通过在等待 await
中引发异常的情况下“恢复”它来完成任务。
一般来说,不等待任何事情的协同程序很好地表明它的编写不正确,并且只是名义上的协同程序。等待是协同程序存在的原因,sleeper
不包含任何。
is there a way to modify behavior to match my intuition?
如果您必须从 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 秒,但这不会影响事件循环,它的工作方式与 [=] 时类似使用了 24=]。
请注意,取消等待 run_in_executor
的协程只会取消在另一个线程中等待阻塞 time.sleep(2)
完成。阻塞调用将继续执行直到完成,这是可以预料的,因为没有中断它的通用机制。
使用 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 会根据协程花费多长时间来超时,而不是根据协程中的各个异步调用。 导致这种行为的幕后发生了什么,是否有办法修改行为以符合我的直觉?
What is going on behind the scenes that results in this behavior
最简单的答案是asyncio是基于合作多任务处理,time.sleep
不合作。 time.sleep(2)
阻塞线程两秒,事件循环等等,任何人都无能为力。
另一方面,asyncio.sleep
很小心written,所以当你await asyncio.sleep(2)
时,它会立即挂起当前任务并安排事件循环在2秒后恢复它. Asyncio 的“休眠”是隐式的,它允许事件循环在协程挂起时继续执行其他任务。相同的挂起系统允许 wait_for
取消任务,事件循环通过在等待 await
中引发异常的情况下“恢复”它来完成任务。
一般来说,不等待任何事情的协同程序很好地表明它的编写不正确,并且只是名义上的协同程序。等待是协同程序存在的原因,sleeper
不包含任何。
is there a way to modify behavior to match my intuition?
如果您必须从 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 秒,但这不会影响事件循环,它的工作方式与 [=] 时类似使用了 24=]。
请注意,取消等待 run_in_executor
的协程只会取消在另一个线程中等待阻塞 time.sleep(2)
完成。阻塞调用将继续执行直到完成,这是可以预料的,因为没有中断它的通用机制。