在异步任务之间自由切换的正确方法是什么?

Oli*_*çon 5 python asynchronous python-3.x python-asyncio

假设我有一些异步运行的任务。它们可能是完全独立的,但我仍然想设置 tak 将暂停的点,以便它们可以同时运行。

并发运行任务的正确方法是什么?我目前正在使用await asyncio.sleep(0),但我觉得这会增加很多开销。

import asyncio

async def do(id, amount):
    for i in range(amount):
        # Do some time-expensive work
        print(f'{id}: has done {i}')

        await asyncio.sleep(0)

    return f'{id}: done'

async def main():
    res = await asyncio.gather(do('Task1', 5), do('Task2', 3))
    print(*res, sep='\n')

loop = asyncio.get_event_loop()

loop.run_until_complete(main())
Run Code Online (Sandbox Code Playgroud)

输出

Task1: has done 0
Task2: has done 0
Task1: has done 1
Task2: has done 1
Task1: has done 2
Task1: done
Task2: done
Run Code Online (Sandbox Code Playgroud)

如果我们使用简单的生成器,emptyyield会在没有任何开销的情况下暂停任务流,但 emptyawait是无效的。

在没有开销的情况下设置此类断点的正确方法是什么?

use*_*342 6

正如评论中提到的,通常 asyncio 协程会在调用时自动挂起,这些调用会在等效的同步代码中阻塞或休眠。在您的情况下,协程受 CPU 限制,因此等待阻塞调用是不够的,它需要偶尔放弃对事件循环的控制以允许系统的其余部分运行。

显式产量在协作多任务处理中并不少见await asyncio.sleep(0),为此目的使用将按预期工作,它确实存在风险:睡眠太频繁,并且您通过不必要的开关减慢计算速度;睡眠太少,并且您在单个协程中花费太多时间来占用事件循环。

asyncio 提供的解决方案是使用run_in_executor. 等待它会自动挂起协程,直到 CPU 密集型任务完成,没有任何中间轮询。例如:

import asyncio

def do(id, amount):
    for i in range(amount):
        # Do some time-expensive work
        print(f'{id}: has done {i}')

    return f'{id}: done'

async def main():
    loop = asyncio.get_event_loop()
    res = await asyncio.gather(
        loop.run_in_executor(None, do, 'Task1', 5),
        loop.run_in_executor(None, do, 'Task2', 3))
    print(*res, sep='\n')

loop = asyncio.get_event_loop()
loop.run_until_complete(main())
Run Code Online (Sandbox Code Playgroud)