为什么我的异步函数没有完成其代码的所有执行?

Why does my asynchronous function not complete all execution of its code?

我刚刚开始学习异步编程,尤其是 asyncawait 语法以及 asyncio 模块的用法。

我的问题是关于以下代码的输出:

import asyncio


async def foo():
    print('start fetching')
    await asyncio.sleep(2)
    print('done fetching')
    return {'data':1}

async def boo():
    for i in range(10):
        print(i)
        await asyncio.sleep(0.25)


async def main():
    task1 = asyncio.create_task(foo())
    task2 = asyncio.create_task(boo())

asyncio.run(main())

我得到的输出是:

start fetching
0

虽然我了解代码的每个单独部分的作用,但我不明白为什么只输出了两行代码。这是我的理解:

您必须等待任务完成:

async def main():
    task1 = asyncio.create_task(foo())
    task2 = asyncio.create_task(boo())

    await task1
    await task2

输出:

start fetching
0
1
2
3
4
5
6
7
done fetching
8
9

有关更多示例,请参阅 official document


现在说说为什么每个函数只打印一行。关键是任何块操作都会导致任务重新调度:

import asyncio

async def foo():
    print('start fetching')
    # Do not await anything
    print('done fetching')
    i = 0
    # Compute for a long time, but the task scheduler
    # will not interrupt it.
    while i < 99999999:
        i += 1
    return {'data':1}

async def boo():
    for i in range(10):
        print(i)
        # Do not await anything

async def main():
    task1 = asyncio.create_task(foo())
    task2 = asyncio.create_task(boo())

asyncio.run(main())

输出:

start fetching
done fetching
0
1
2
3
4
5
6
7
8
9

如您所见,这些任务按顺序完成。

async def foo():
    print('start fetching')
    # Although it actually not sleep,
    # it still force the scheduler to switch tasks.
    await asyncio.sleep(0)
    print('done fetching')
    return {'data':1}

async def boo():
    for i in range(10):
        print(i)

async def main():
    task1 = asyncio.create_task(foo())
    task2 = asyncio.create_task(boo())
    await asyncio.sleep(0)

asyncio.run(main())

输出:

start fetching
0
1
2
3
4
5
6
7
8
9
done fetching

如果任何任务未完成,脚本终止时,那些未完成的任务将被丢弃。

如果您想更深入地了解调度程序,也许您应该阅读 asyncio 的源代码。

asyncio.run()内部有两个额外的事件循环迭代,它们用于关闭:

    loop.run_until_complete(loop.shutdown_asyncgens())
    loop.run_until_complete(loop.shutdown_default_executor())

运行 这些调用使之前计划的任务有机会迭代最多 2 次。

注意:这是一个实现细节,随时可能会改变(事件循环startup/shutdown的迭代次数以后可以修改Python 个版本)。

请不要依赖代码中的这种行为,而是明确等待计划任务以正确处理它们。