代码之家  ›  专栏  ›  技术社区  ›  Olivier Melançon iacob

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

  •  1
  • Olivier Melançon iacob  · 技术社区  · 7 年前

    假设我有一些异步运行的任务。它们可能是完全独立的,但我仍然希望设置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())
    

    输出

    Task1: has done 0
    Task2: has done 0
    Task1: has done 1
    Task2: has done 1
    Task1: has done 2
    Task1: done
    Task2: done
    

    如果我们使用简单的发电机,一个空的 yield 会在没有任何开销的情况下暂停任务流,但为空 await 无效。

    1 回复  |  直到 7 年前
        1
  •  4
  •   user4815162342    6 年前

    正如在注释中提到的,通常异步协同路由会自动挂起那些将阻塞或休眠在等效同步咖啡中的调用。在您的例子中,协程是CPU受限的,因此等待阻塞调用是不够的,它需要偶尔将控制权交给事件循环,以允许系统的其余部分运行。

    await asyncio.sleep(0) 为此,我将 work as intended ,它确实带来了风险:睡眠太频繁,并且通过不必要的切换来降低计算速度;睡眠太少,并且在单个协同进程中花费太多时间,从而占用了事件循环。

    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())