协程与任务

Posted 哦...

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了协程与任务相关的知识,希望对你有一定的参考价值。

在开始之前先明确两点:

  1. 使用async def定义协程函数/协程是目前推荐的方式。
  2. asyncio是一个帮助开发者进行异步编程的工具库。

1. 协程

Python3.5之后应通过 async/await 语法来声明协程函数/协程。

>>> import asyncio

>>> async def main():
...     print('hello')
...     await asyncio.sleep(1)
...     print('world')

>>> asyncio.run(main())
hello
world

就像有yield的函数就不是函数,“调用”函数时获得的是一个生成器对象。用async def定义的函数称为协程函数,“调用”协程函数时获得的是一个协程对象(coroutine object)。

>>> main()
<coroutine object main at 0x1053bb7c8>

要想让协程运行获得结果,必须为协程提供事件总线。asyncio提供了三种方式方便协程的运行

第一种方式:asyncio.run(协程)

>>> asyncio.run(main())
hello
world

 第二种方式:await 协程

import asyncio
import time

async def say_after(delay, what):
    await asyncio.sleep(delay)
    print(what)

async def main():
    print(f"started at time.strftime('%X')")

    await say_after(1, 'hello')
    await say_after(2, 'world')

    print(f"finished at time.strftime('%X')")

asyncio.run(main())

await有一定的局限性,await必须写在一个async def中,也就是要写在另一个协程中。

例子中main()是一个协程,say_after是一个协程。用第一种方式执行main()协程时,用第二种方式在main()协程内部完成了两个say_after()协程的执行。

第三种方式:task = asyncio.create_task(协程)将协程包装为Task类型的对象。Task类型是Future(期物)类型的子类。

async def main():
    task1 = asyncio.create_task(say_after(1, 'hello'))

    task2 = asyncio.create_task(say_after(2, 'world'))

    print(f"started at time.strftime('%X')")

    # Wait until both tasks are completed (should take around 2 seconds.)
    await task1
    await task2

    print(f"finished at time.strftime('%X')")

修改main(),利用asyncio提供的工具函数create_task()将协程say_after()包装为Task对象。但是Task对象的执行也得用await的方式执行。

task1、task2任务会在get_running_loop()返回的事件总线中执行,如果当前线程没有在运行的事件总线则会引发RuntimeError。

所以,到底什么是可以写在await后面的呢?

2. 可等待的(Awaitable)

如果一个对象可以在await语句中使用,那么它就是 可等待 对象。许多 asyncio API 都被设计为接受可等待对象,包括协程(Coroutine)任务(Task) 和 期物(Future)对象。

期物相较协程、任务来说是一种特殊的低层级可等待对象,表示一个异步操作的最终结果

期物最典型的应用场景是通过asyncio提供的run_in_executor函数,该函数将一个非异步阻塞函数通过扔到线程池中执行,同时将该函数包装为一个Future(期物)对象返回。

注意,在异步执行上下文中,绝对不能出现非异步阻塞函数,因为它会阻塞线程,进而阻塞事件总线对总线上任务的执行。

3. 并发执行批量任务

awaitable asyncio.gather(*awsreturn_exceptions=False并发 运行aws序列中的可等待对象。注意传参的方式,是依次传入awaitable1,awaitable2,awaitable3...

asyncio的gather与线程池的map有异曲同工之妙。只不过一个是以单线程异步方式执行任务(期物对象的执行另当别论),一个是多线程执行任务,最后执行的结果都以序列作为容器,以任务添加顺序作为下标,有序呈现。

import asyncio

async def factorial(name, number):
    f = 1
    for i in range(2, number + 1):
        print(f"Task name: Compute factorial(number), currently i=i...")
        await asyncio.sleep(1)
        f *= i
    print(f"Task name: factorial(number) = f")
    return f

async def main():
    # Schedule three calls *concurrently*:
    L = await asyncio.gather(
        factorial("A", 2),
        factorial("B", 3),
        factorial("C", 4),
    )
    print(L)

asyncio.run(main())

逢await时会将执行上下文给予事件总线。

# Expected output:
#
#     Task A: Compute factorial(2), currently i=2...
#     Task B: Compute factorial(3), currently i=2...
#     Task C: Compute factorial(4), currently i=2...
#     Task A: factorial(2) = 2
#     Task B: Compute factorial(3), currently i=3...
#     Task C: Compute factorial(4), currently i=3...
#     Task B: factorial(3) = 6
#     Task C: Compute factorial(4), currently i=4...
#     Task C: factorial(4) = 24
#     [2, 6, 24]

4.超时与等待

coroutine asyncio.wait(aws*timeout=Nonereturn_when=ALL_COMPLETED)

并发地运行参数中的可等待对象们 并进入阻塞状态直到满足 return_when 所指定的条件。

aws 可迭代对象必须不为空。返回两个 Task/Future 集合: (done, pending)

done, pending = await asyncio.wait(aws)

 coroutine asyncio.wait_for(awtimeout)等待aw 完成,指定 timeout 秒数后超时。

如果 aw 是一个协程,它将自动被作为任务调度。如果发生超时,任务将取消并引发asyncio.TimeOutError。

以上部大部分案例与内容摘自Python官网,有编辑。

以上是关于协程与任务的主要内容,如果未能解决你的问题,请参考以下文章

协程与通道

-2-协程与异步

GoLang协程与通道---下

网络编程之协程与池

Go协程与协程池

Go协程与协程池