Python 3.5 中协程和未来/任务之间的区别?

新手上路,请多包涵

假设我们有一个虚拟函数:

 async def foo(arg):
    result = await some_remote_call(arg)
    return result.upper()

有什么区别:

 import asyncio

coros = []
for i in range(5):
    coros.append(foo(i))

loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(coros))

和:

 import asyncio

futures = []
for i in range(5):
    futures.append(asyncio.ensure_future(foo(i)))

loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(futures))

_注意_:该示例返回结果,但这不是问题的重点。当返回值很重要时,使用 gather() 而不是 wait()

无论返回值如何,我都在寻找 ensure_future() 的清晰度。 wait(coros)wait(futures) 都运行协程,那么什么时候以及为什么应该将协程包装在 ensure_future 中?

基本上,使用 Python 3.5 的 async 运行一堆非阻塞操作的正确方法 ™ 是什么?

对于额外的信用,如果我想批量调用怎么办?例如,我需要调用 some_remote_call(...) 1000 次,但我不想用 1000 个并发连接压垮 Web 服务器/数据库/等。这对于线程或进程池是可行的,但是有没有办法用 asyncio 来做到这一点?

2020 年更新(Python 3.7+) :不要使用这些片段。而是使用:

 import asyncio

async def do_something_async():
    tasks = []
    for i in range(5):
        tasks.append(asyncio.create_task(foo(i)))
    await asyncio.gather(*tasks)

def do_something():
    asyncio.run(do_something_async)

还可以考虑使用 Trio ,这是 asyncio 的强大的第三方替代方案。

原文由 knite 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 766
2 个回答

Vincent 的评论链接到 https://github.com/python/asyncio/blob/master/asyncio/tasks.py#L346 ,这表明 wait() 将协程包装在 ensure_future() 为你!

换句话说,我们确实需要一个未来,协程会默默地转化为它们。

当我找到关于如何批处理协程/期货的明确解释时,我会更新这个答案。

原文由 knite 发布,翻译遵循 CC BY-SA 3.0 许可协议

协程是一个生成器函数,它既可以产生值,也可以从外部接受值。使用协程的好处是我们可以暂停函数的执行并在稍后恢复它。在网络操作的情况下,在我们等待响应时暂停函数的执行是有意义的。我们可以利用这段时间来运行一些其他功能。

未来就像来自 Javascript 的 Promise 对象。它就像一个未来将要实现的价值的占位符。在上述情况下,在等待网络 I/O 时,一个函数可以给我们一个容器,承诺在操作完成时它将用值填充容器。我们持有 future 对象,当它完成时,我们可以调用它的方法来检索实际结果。

直接回答: 如果不需要结果,则不需要 ensure_future 。如果您需要结果或检索发生的异常,它们很好。

额外学分: 我会选择 run_in_executor 并传递一个 Executor 实例来控制最大工人数。

解释和示例代码

在第一个示例中,您使用的是协程。 wait 函数采用一堆协程并将它们组合在一起。所以 wait() 在所有协程用完时完成(完成/完成返回所有值)。

 loop = get_event_loop() #
loop.run_until_complete(wait(coros))

run_until_complete 方法将确保循环在执行完成之前处于活动状态。请注意在这种情况下您是如何得不到异步执行的结果的。

在第二个示例中,您正在使用 ensure_future 函数来包装协程并返回 Task 对象,它是一种 Future .当您调用 ensure_future 时,协程计划在主事件循环中执行。返回的 future/task 对象还没有值,但随着时间的推移,当网络操作完成时,future 对象将保存操作的结果。

 from asyncio import ensure_future

futures = []
for i in range(5):
    futures.append(ensure_future(foo(i)))

loop = get_event_loop()
loop.run_until_complete(wait(futures))

所以在这个例子中,我们做同样的事情,除了我们使用期货而不是仅仅使用协程。

让我们看一个如何使用 asyncio/coroutines/futures 的例子:

 import asyncio

async def slow_operation():
    await asyncio.sleep(1)
    return 'Future is done!'

def got_result(future):
    print(future.result())

    # We have result, so let's stop
    loop.stop()

loop = asyncio.get_event_loop()
task = loop.create_task(slow_operation())
task.add_done_callback(got_result)

# We run forever
loop.run_forever()

在这里,我们在 loop 对象上使用了 create_task 方法。 ensure_future 将在主事件循环中安排任务。这种方法使我们能够在我们选择的循环上安排协程。

我们还看到了在任务对象上使用 add_done_callback 方法添加回调的概念。

A Taskdone 当协程返回一个值、引发异常或被取消时。有一些方法可以检查这些事件。

我写了一些关于这些主题的博客文章,它们可能会有所帮助:

当然,你可以在官方手册上找到更多细节: https ://docs.python.org/3/library/asyncio.html

原文由 masnun 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题