等待总是给其他任务一个执行的机会吗? [英] Does await always give other tasks a chance to execute?
问题描述
我想知道当事件循环切换任务时python会提供什么保证.
I'd like to know what guarantees python gives around when a event loop will switch tasks.
据我了解,async
/await
与线程有显着不同,因为事件循环不会基于时间切片来切换任务,这意味着除非任务产生(await
),否则它将无限期地进行.这实际上很有用,因为在异步环境下管理关键部分比在线程管理中要容易得多.
As I understand it async
/ await
are significantly different from threads in that the event loop does not switch task based on time slicing, meaning that unless the task yields (await
), it will carry on indefinitely. This can actually be useful because it is easier to manage critical sections under asyncio than with threading.
我不太了解的是以下内容:
What I'm less clear about is something like the following:
async def caller():
while True:
await callee()
async def callee():
pass
在此示例中,caller
重复为await
.因此,从技术上讲,它是屈服的.但是我尚不清楚这是否将允许事件循环上的其他任务执行,因为它只会屈服于callee
而从未屈服.
In this example caller
is repeatedly await
. So technically it is yielding. But I'm not clear on whether this will allow other tasks on the event loop to execute because it only yields to callee
and that is never yielding.
也就是说,即使我知道它不会阻塞,如果我在关键区域"内等待callee
,是否有发生其他意外情况的危险?
That is if I awaited callee
inside a "critical section" even though I know it won't block, am I at risk of something else unexpected happening?
推荐答案
您警惕是正确的. caller
从callee
产生,并产生事件循环.然后,事件循环决定要恢复的任务. (希望)在调用callee
之间可以压缩其他任务. callee
需要等待实际的阻塞Awaitable
,例如asyncio.Future
或asyncio.sleep()
,不是协程,否则控件将不会返回到事件循环,直到caller
返回.
You are right to be wary. caller
yields from callee
, and yields to the event loop. Then the event loop decides which task to resume. Other tasks may (hopefully) be squeezed in between the calls to callee
. callee
needs to await an actual blocking Awaitable
such as asyncio.Future
or asyncio.sleep()
, not a coroutine, otherwise the control will not be returned to the event loop until caller
returns.
例如,以下代码将在开始处理caller1
任务之前完成caller2
任务.由于callee2
本质上是一个同步函数,无需等待阻塞的I/O操作,因此,不会创建任何挂起点,并且caller2
将在每次调用callee2
之后立即恢复.
For example, the following code will finish the caller2
task before it starts working on the caller1
task. Because callee2
is essentially a sync function without awaiting a blocking I/O operations, therefore, no suspension point is created and caller2
will resume immediately after each call to callee2
.
import asyncio
import time
async def caller1():
for i in range(5):
await callee1()
async def callee1():
await asyncio.sleep(1)
print(f"called at {time.strftime('%X')}")
async def caller2():
for i in range(5):
await callee2()
async def callee2():
time.sleep(1)
print(f"sync called at {time.strftime('%X')}")
async def main():
task1 = asyncio.create_task(caller1())
task2 = asyncio.create_task(caller2())
await task1
await task2
asyncio.run(main())
结果:
sync called at 19:23:39
sync called at 19:23:40
sync called at 19:23:41
sync called at 19:23:42
sync called at 19:23:43
called at 19:23:43
called at 19:23:44
called at 19:23:45
called at 19:23:46
called at 19:23:47
但是,如果callee2
唤醒,如下所示,即使等待asyncio.sleep(0)
,任务切换也会发生,并且任务将同时运行.
But if callee2
awaits as the following, the task switching will happen even if it awaits asyncio.sleep(0)
, and the tasks will run concurrently.
async def callee2():
await asyncio.sleep(1)
print('sync called')
结果:
called at 19:22:52
sync called at 19:22:52
called at 19:22:53
sync called at 19:22:53
called at 19:22:54
sync called at 19:22:54
called at 19:22:55
sync called at 19:22:55
called at 19:22:56
sync called at 19:22:56
这种行为不一定是直观的,但是考虑到asyncio
是同时处理I/O操作和联网的,而不是通常的同步python代码,所以这是有道理的.
This behavior is not necessarily intuitive, but it makes sense considering that asyncio
was made to handle I/O operations and networking concurrently, not the usual synchronous python codes.
要注意的另一件事是:如果callee
等待一个协程,然后依次等待一个asyncio.Future
,asyncio.sleep()
或另一个协程,这些协程等待链中的这些事情之一,则此方法仍然有效.等待阻塞Awaitable
时,流控制将返回到事件循环.因此,以下内容也适用.
Another thing to note is: This still works if the callee
awaits a coroutine that, in turn, awaits a asyncio.Future
, asyncio.sleep()
, or another coroutine that await one of those things down the chain. The flow control will be returned to the event loop when the blocking Awaitable
is awaited. So the following also works.
async def callee2():
await inner_callee()
print(f"sync called at {time.strftime('%X')}")
async def inner_callee():
await asyncio.sleep(1)
这篇关于等待总是给其他任务一个执行的机会吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!