等待总是给其他任务一个执行的机会吗? [英] Does await always give other tasks a chance to execute?

查看:70
本文介绍了等待总是给其他任务一个执行的机会吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想知道当事件循环切换任务时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?

推荐答案

您警惕是正确的. callercallee产生,并产生事件循环.然后,事件循环决定要恢复的任务. (希望)在调用callee之间可以压缩其他任务. callee需要等待实际的阻塞Awaitable,例如asyncio.Futureasyncio.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.Futureasyncio.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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆