首页 文章

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

提问于
浏览
84

假设我们有一个虚函数:

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

有什么区别:

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

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

和:

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

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

无论返回值如何,我都在寻找 ensure_future() 的清晰度 . wait(coros)wait(futures) 都运行协同程序,所以何时以及为什么要将协程包装在 ensure_future 中?

基本上是什么's the Right Way (tm) to run a bunch of non-blocking operations using Python 3.5' s async

如果我想批量通话,如果需要额外的积分?例如,我需要调用 some_remote_call(...) 1000次,但我不想粉碎web服务器/数据库/等,同时连接1000个 . 这对于线程或进程池是可行的,但有没有办法用 asyncio 做到这一点?

4 回答

  • 7

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

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

    Direct Answer: 如果您不需要结果,则不需要 ensure_future . 如果您需要结果或检索发生的异常,它们就很好 .

    Extra Credits: 我会选择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 /期货的例子:

    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 方法添加回调的概念 .

    当协同程序返回值,引发异常或被取消时, Taskdone . 有方法可以检查这些事件 .

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

    当然,您可以在官方手册中找到更多详细信息:https://docs.python.org/3/library/asyncio.html

  • 0

    文森特的评论链接到https://github.com/python/asyncio/blob/master/asyncio/tasks.py#L346,这表明 wait() 包装了 ensure_future() 中的协程!

    换句话说,我们确实需要一个未来,协同作用将被默默地转化为它们 .

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

  • 10

    简单的答案是

    • 调用croutine函数( async def )不运行它 . 它只返回协程对象,就像生成器函数返回生成器对象一样 .

    • await 从协程中检索值,即调用协程

    • eusure_future/create_task 安排协程在下一次迭代时在事件循环上运行(尽管不等待它们完成,就像一个守护程序线程) .

    一些代码示例

    我们先来说清楚一些术语:

    • coroutine函数,你 async def

    • coroutine,你得到了什么当你调用一个corotine函数

    似乎下面的评论 .

    案例1,等待协程

    我们创建了两个协同程序,其中一个使用create_task运行另一个协同程序 .

    import asyncio
    import time
    
    # coroutine function
    async def p(word):
        print(f'{time.time()} - {word}')
    
    
    async def main():
        loop = asyncio.get_event_loop()
        coro = p('await')  # coroutine
        task2 = loop.create_task(p('create_task'))  # <- runs in next iteration
        await coro  # <-- run directly
        await task2
    
    if __name__ == "__main__":
        loop = asyncio.get_event_loop()
        loop.run_until_complete(main())
    

    你会得到结果:

    1539486251.7055213 - await
    1539486251.7055705 - create_task
    

    说明:

    task1直接执行,task2在下一次迭代中执行 .

    案例2,产生对事件循环的控制

    如果我们替换main函数,我们可以看到不同的结果:

    async def main():
        loop = asyncio.get_event_loop()
        coro = p('await')
        task2 = loop.create_task(p('create_task'))  # scheduled to next iteration
        await asyncio.sleep(1)  # loop got control, and runs task2
        await coro  # run coro
        await task2
    

    你会得到结果:

    -> % python coro.py
    1539486378.5244057 - create_task
    1539486379.5252144 - await  # note the delay
    

    说明:

    当调用 asyncio.sleep(1) 时,控件被返回到事件循环,循环检查要运行的任务,然后它运行由 create_task 创建的任务 .

    注意,我们首先调用corotine函数,但不是 await 它,所以我们只创建了一个corotine,而不是让它运行 . 然后,我们再次调用corotine函数,并将其包装在 create_task 调用中,creat_task将按顺序安排协程在下一次迭代时运行 . 因此,在结果中, create taskawait 之前执行 .

    实际上,这里的重点是将控制权交还给循环,您可以使用 asyncio.sleep(0) 来查看相同的结果 .

    引擎盖下

    loop.create_task 实际上调用了 asyncio.tasks.Task() ,它将调用 loop.call_soon . loop.call_soon 将把任务放在 loop._ready 中 . 在循环的每次迭代期间,它会检查loop._ready中的每个回调并运行它 .

    asyncio.waitasyncio.eusure_futureasyncio.gather 直接或间接地调用 loop.create_task .

    另请注意docs

    回调按照注册顺序调用 . 每个回调将被调用一次 .

  • 76

    From the BDFL [2013]

    任务

    • 这是一个包裹在未来的协程

    • class Task是Future类的子类

    • 所以它也适用于 await


    • 它与裸露的协程有什么不同?

    • 它可以在不等待的情况下取得进展

    • 只要你等待别的东西,即

    • await [something_else]

    考虑到这一点, ensure_future 作为创建任务的名称是有意义的,因为未来的结果将被计算,无论你是否 await 它(只要你等待某事) . 这允许事件循环在您等待其他事情时完成您的任务 . 请注意,在Python 3.7中 create_task 是首选方式ensure a future .

    注意:为了现代性,我在Guido的幻灯片中将"yield from"更改为"await" .

相关问题