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

时间:2018-09-06 17:21:23

标签: python python-3.x asynchronous python-asyncio

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

同时运行任务的正确方法是什么?我目前正在使用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 个答案:

答案 0 :(得分:3)

如评论中所述,通常异步协程会在可能阻塞或休眠等效同步咖啡的呼叫上自动挂起。在您的情况下,协程是受CPU约束的,因此仅等待阻塞调用是不够的,因此它需要偶尔将控制权放到事件循环中,以允许系统的其余部分运行。

在协同多任务处理中,显式收益并不罕见,为此使用await asyncio.sleep(0)work as intended带来风险:睡眠过多,并且通过不必要的切换来减慢计算速度;很少睡觉,而通过在一个协程中花费太多时间来拖累事件循环。

asyncio提供的解决方案是使用run_in_executor将CPU绑定的代码卸载到线程池中。等待它会自动暂停协程,直到完成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())