调用 ensure_future 是包装协程结果的正确方法吗?

Is calling ensure_future the right way to wrap up the results of coroutines?

我仍然不确定何时使用 ensure_future,我想知道在这种情况下我是否会 want/need 它。

考虑以下代码

import asyncio


loop = asyncio.get_event_loop()


async def do_work(count):
    for i in range(count):
        if i % 10000 == 0:
            await asyncio.sleep(0.1)

    print(count)
    return i


async def do_batch_work():
    res1 = do_work(100000)
    res2 = do_work(100)
    await asyncio.wait([res1, res2])
    return res1, res2


def main():
    loop = asyncio.get_event_loop()
    res1, res2 = loop.run_until_complete(do_batch_work())
    print(res1.result())
    print(res2.result())
    loop.close()

main()

这会打印:

100
100000
Traceback (most recent call last):
  File "more_async.py", line 30, in <module>
    main()
  File "more_async.py", line 26, in main
    print(res1.result())
AttributeError: 'coroutine' object has no attribute 'result'

异步代码按预期运行并按预期顺序打印出来,但 loop.run_until_complete(...) 不让我访问底层结果,因为 coroutine 对象似乎没有获取结果的方法。

我可以通过如下更改方法来解决这个问题

async def do_batch_work():
    res1 = asyncio.ensure_future(do_work(100000))
    res2 = asyncio.ensure_future(do_work(100))
    await asyncio.wait([res1, res2])
    return res1, res2

通过调用 asyncio.ensure_future(...),我确保 Tasks 回到我可以调用 result() 的地方。

我想知道,这是正确的处理方式吗?如果我关心协程的结果或者是否有其他我不知道的方法,是否需要使用 ensure_future

async def do_batch_work():
    res1 = do_work(100000)
    res2 = do_work(100)
    await asyncio.wait([res1, res2])
    return res1, res2

res1res2 在该示例中只是协程,而不是 Future 对象,因此没有结果属性。当您使用 ensure_future() 时,res1res2 现在是 Future 对象,这就是您可以访问结果属性的原因。 wait() 在这两种情况下的工作方式相同,并且两种代码的结果相同,只是您的函数没有 return 正确的对象。

如果您想修改您的第一个示例,您想要的 Future 个对象被 wait() 编辑为第一个项目。

async def do_batch_work():
    res1 = do_work(100000)
    res2 = do_work(100)
    (res1, res2), _ = await asyncio.wait([res1, res2])
    return res1, res2

您的代码非常好 - 它可以正常工作,而且我认为 asyncio 仍然没有制定一些强有力的标准。但是如果你只是想 运行 协程并行,还有另一种方式,而且见得比较多。

asyncio.gather().

Gather 的文档是不言自明的,它 returns 未来带有结果列表,这里是您的代码示例:

async def do_batch_work():
    res1 = do_work(100000)
    res2 = do_work(100)
    return await asyncio.gather(res1, res2)


def main():
    loop = asyncio.get_event_loop()
    res1, res2 = loop.run_until_complete(do_batch_work())
    print(res1)
    print(res2)
    loop.close()