Python之协程与任务

  近期在学习协程,为了加深理解,本文参考官网文档:https://docs.python.org/zh-cn/3.8/library/asyncio-task.html

  协程与任务

  本节简述用于协程与任务的高层级API

  协程

  协程 通过 async/await 语法进行声明,是编写 asyncio 应用的推荐方式。 例如,以下代码段(需要 Python 3.7+)会打印 "hello",等待 1 秒,再打印 "world":

import asyncio
import time
# async/await语法 start
async def main():
    print('hello')
    await asyncio.sleep(1)
    print('world')

asyncio.run(main())
# hello
# world
# async/await语法 end

  注意:简单地调用一个协程并不会将其加入执行日程

  会返回一个协程对象

print(main())
<coroutine object main at 0x00000211DF3AB6C0>

  要真正运行一下协程,asyncio提供了三种主要机制:

  • asyncio.run()函数用来运行最高层级的入口点"main()"函数(参见上面的示例)
  • await等待一个协程。协程函数运行到await则会等待await后对象的运行结果。以下代码段会在等待 1 秒后打印 "hello",然后 再次 等待 2 秒后打印 "world":
    # await start
    # 运行主入口协程函数main(),前后打印运行事件
    # 中间运行两次协程函数say_after(delay,what)
    # 传递的参数不同,所以等待的时候不同
    # 首先等待1秒打印hello,然后等待2秒打印world
    async def say_after(delay,what):
        await asyncio.sleep(delay)
        print(what)
    
    async def main():
        print(f"start 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 end
    

      预期的输出如下

    start at 09:13:20
    hello
    world
    finished at 09:13:23
    

      可以看到运行的时间为3秒

  • asyncio.create_task() 函数用来并发运行作为 asyncio 任务 的多个协程。

  让我们修改以上示例,并发 运行两个 say_after 协程:

# asyncio.create_task()并发运行两个say_after start
async def say_after(delay,what):
    await asyncio.sleep(delay)
    print(what)

async def main():
    task1 = asyncio.create_task(say_after(1, 'hello'))
    task2 = asyncio.create_task(say_after(2, 'world'))
    print(f"start at {time.strftime('%X')}")
    # 等待两个任务运行完毕,应该等待2秒
    await task1
    await task2
    print(f"finished at {time.strftime('%X')}")
   
asyncio.run(main())
# asyncio.create_task()并发运行两个say_after start

  注意,预期的输出显示代码段的运行时间比之前快了 1 秒:

start at 09:16:58
hello
world
finished at 09:17:00

  分析:第一次代码运行是在同一个任务内,所以代码还是顺序运行,运行时间为运行等待的两次时间相加1+2=3秒,第二次使用任务两个任务几乎同时运行即运行第一个任务的时候第二个任务同时也启动了,所以消耗的时候为2秒,即第一个任务运行完毕在等待1秒第二个任务也运行完毕了

  以下代码和上面代码实现的效果一致,把任务放在一个列表内再运行

# 以下代码和上面代码效果一样 start
loop = asyncio.new_event_loop()
tasks = [say_after(1, 'hello'),say_after(2, 'world')]
print(f"start at {time.strftime('%X')}")
loop.run_until_complete(asyncio.wait(tasks))
print(f"finished at {time.strftime('%X')}")
# 以下代码和上面代码效果一样 end

  可等待对象

  如果一个对象可以在 await 语句中使用,那么它就是 可等待 对象。许多 asyncio API 都被设计为接受可等待对象。

  可等待 对象有三种主要类型: 协程, 任务 和 Future.

  协程

  Python协程属于可等待对象,因此可以在其他协程中被等待:

# 协程是可等待对象
import asyncio
async def netsted():
    return 42

async def main():
    # 如果直接调用netsted()不会发生什么
    # 一个协程对象创建但是不等待
    # 所以它是不会运行的
    netsted()

    # 让我们做一些不同的操作等待它
    # 预期输出为协程的返回"42"
    print(await netsted())
asyncio.run(main())
# 42

  输出如下,直接运行协程会报一个警告RuntimeWarning

  如果使用参数await则会等待协程运行完毕,返回结果,本次定义的返回为42使用print打印出来

d:/learn-python3/学习脚本/协程系列/协程与任务.py:68: RuntimeWarning: coroutine 'netsted' was never awaited
  netsted()
RuntimeWarning: Enable tracemalloc to get the object allocation traceback
42

  重要:在本文档中‘协程’可用来表示两个紧密关联的概念:

  协程函数:定义形式为async def的函数

  协程对象:调用协程函数所返回的对象

  任务

  任务被用来设置日程以便并发执行协程。

  当一个协程通过asyncio.create_task()等函数被打包为一个任务,该协程将自动排入日程准备立即运行:

# 任务是可等待对象
import asyncio

async def netsted():
    return 42

async def main():
    # Schedule nested() to run soon concurrently
    # with "main()".
    # 日程安排将并发执行使用main()调用
    task = asyncio.create_task(netsted())

    # "task" can now be used to cancel "nested()", or
    # can simply be awaited to wait until it is complete:
    # 任务取消使用netsted(),等待任务执行直到任务完成
    await task

asyncio.run(main())

  以上代码无输出,因为没有使用print打印await task的返回结果

  Futures

  Future 是一种特殊的 低层级 可等待对象,表示一个异步操作的 最终结果

  当一个 Future 对象 被等待,这意味着协程将保持等待直到该 Future 对象在其他地方操作完毕。

  在 asyncio 中需要 Future 对象以便允许通过 async/await 使用基于回调的代码。

  通常情况下 没有必要 在应用层级的代码中创建 Future 对象。

  Future 对象有时会由库和某些 asyncio API 暴露给用户,用作可等待对象:

# Future是可等待对象
async def main():
    await function_that_returns_a_future_object()

    # this is also valid:
    await asyncio.gather(
        function_that_returns_a_future_object(),
        some_python_coroutine()
    )

  一个很好的返回对象的低层级函数的示例是 loop.run_in_executor()

  运行asyncio程序

  asyncio.run(coro,*,debug=False)

  执行coroutine coro并返回结果。

  次函数运行传入的协程,负责管理asyncio事件循环并完结异步生成器。

  当有其他asyncio事件循环在同一线程中运行时,次函数不能被调用。

  如果debug为True,事件循环将以调试模式运行。

  此函数总是会被创建一个新的事件循环并在结束时关闭之。它应当被用作asyncio程序的主入口点,理想情况下应当只被调用一次。

  示例:

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

asyncio.run(main())

  创建任务

  asyncio.create_task(coro,*,name=None)

  将coro协程打包为一个Task排入日程准备执行。返回Task对象

  name不为None,它将使用Task.set_name()来设为任务的名称。如果name有定义则任务名称为name定义的名称,如果默认我None则任务名称为Task-1 Task-2以此类推

  改任务会在get_running_loop()返回的循环中执行,如果当前线程没有在运行的循环则会引发RuntimeError。

  此函数在Python3.7中被加入。在Python3.7之前,可以改用低层级的asyncio.ensure_future()函数

  示例:

# 创建任务 start
async def coro():
    pass

async def main():
    # 创建task
    task = asyncio.create_task(coro())
    # 获取当前运行的事件循环
    loop = asyncio.get_running_loop()
    print(task)
    # 可以适用于Pythong所有版本但是可读性较差
    task = asyncio.ensure_future(coro())
    print(task)
    print(loop)

asyncio.run(main())
# 创建任务 end

  输出如下,第一个task是使用create_task()创建的的3.7版本之后才有的,第二个task的使用ensure_future()创建。

<Task pending name='Task-7' coro=<coro() running at d:/learn-python3/学习脚本/协程系列/协程与任务.py:116>>
<Task pending name='Task-8' coro=<coro() running at d:/learn-python3/学习脚本/协程系列/协程与任务.py:116>>
<ProactorEventLoop running=True closed=False debug=False>

  休眠

  葱肉time asyncio.sleep(delay,result=None,*,loop=None)

  阻塞delay指定的秒数

  如果指定了result,则当前协程完成时将其返回给调用者。  

  sleep()总是会挂起当前任务,以允许其他任务运行。

  示例

  默认asyncio.sleep()返回为None可以定义一个result返回

async def main():
    result = await asyncio.sleep(1,result=42)
    print(result)

asyncio.run(main())

  输出

42

  以下协程示例运行5秒,每秒显示异常当前时间

async def display_date():
    loop = asyncio.get_running_loop()
    end_time = loop.time() + 5
    while True:
        print(datetime.datetime.now())
        if (loop.time() + 1.0) >= end_time:
            break
        await asyncio.sleep(1)

asyncio.run(display_date())

  输出如下

2021-10-20 11:01:03.763620
2021-10-20 11:01:04.767559
2021-10-20 11:01:05.782350
2021-10-20 11:01:06.795413
2021-10-20 11:01:07.798010

  解析:

  asyncio.get_running_loop()获取当前事件循环对象,loop.time()返回一个浮点数,然后加5,while循环打印当前时间,loop.time()然后加1,判断是否大于设置的结束end_time如果大于或者等于则结束while循环,然后等待1秒继续执行下一个循环。

  并发运行任务

  awaitable asyncio.gather(*awsloop=Nonereturn_exceptions=False)

  并发 运行 aws 序列中的 可等待对象

  如果 aws 中的某个可等待对象为协程,它将自动作为一个任务加入日程。

  如果所有可等待对象都成功完成,结果将是一个由所有返回值聚合而成的列表。结果值的顺序与 aws 中可等待对象的顺序一致。

  如果 return_exceptions 为 False (默认),所引发的首个异常会立即传播给等待 gather() 的任务。aws 序列中的其他可等待对象 不会被取消 并将继续运行。

  如果 return_exceptions 为 True,异常会和成功的结果一样处理,并聚合至结果列表。

  如果 gather() 被取消,所有被提交 (尚未完成) 的可等待对象也会 被取消。

  如果 aws 序列中的任一 Task 或 Future 对象 被取消,它将被当作引发了 CancelledError 一样处理 -- 在此情况下 gather() 调用 不会 被取消。这是为了防止一个已提交的 Task/Future 被取消导致其他 Tasks/Future 也被取消。

  示例:

import asyncio

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

async def main():
    # Schedule three calls *concurrently*:
    # 同时运行3个阶乘
    await asyncio.gather(
        factorial("A", 2),
        factorial("B", 3),
        factorial("C", 4),
    )

asyncio.run(main())

  预期输出如下

Task A: Compute factorial(2)...
Task B: Compute factorial(2)...
Task C: Compute factorial(2)...
Task A: factorial(2) = 2
Task B: Compute factorial(3)...
Task C: Compute factorial(3)...
Task B: factorial(3) = 6
Task C: Compute factorial(4)...
Task C: factorial(4) = 24

  解析:本次生成器用于计算阶乘即传递整数n计算 1 * 2 * ...*n的值,本次使用并行同时计算2 3 4的阶乘,为了方便,我们在任务中仅仅保留计算阶乘2的任务进行分析 

  注释下面两行代码

factorial("B", 3),
factorial("C", 4),

  调试模式分析执行过程

 

 

 

 

 

 

   执行结束

  同理如果需要计算3的阶乘输出如下

  循环两次第一次计算2的阶乘,得到结果以后再计算3的阶乘,循环结束输出结果

Task B: Compute factorial(2)...
Task B: Compute factorial(3)...
Task B: factorial(3) = 6

  计算4的阶乘输出如下

Task C: Compute factorial(2)...
Task C: Compute factorial(3)...
Task C: Compute factorial(4)...
Task C: factorial(4) = 24

  再来分析同时计算2,3,4阶乘的输出

   注解:如果 return_exceptions 为 False,则在 gather() 被标记为已完成后取消它将不会取消任何已提交的可等待对象。 例如,在将一个异常传播给调用者之后,gather 可被标记为已完成,因此,在从 gather 捕获一个(由可等待对象所引发的)异常之后调用 gather.cancel() 将不会取消任何其他可等待对象。

  在 3.7 版更改: 如果 gather 本身被取消,则无论 return_exceptions 取值为何,消息都会被传播。

  个人注解:以上看不懂,回头在来理解

  屏蔽取消操作 

  awtable asyncio.shield(aw,*,loop=None)

   保护一个可等待对象防止其被取消
  如果aw是一个协程,它将自动转为任务加入日程。
  以下语句
res = await shield(something())

  相当于

res = await something()

  不同之处在于如果包含它的协程被取消,在somethin()中运行的任务不会被取消。从something()的角度来看,取消操作并没有发生。然而其调用者已被取消,因此“await”表达式仍然会引发CancelledError

  如果通过其他方式取消something()(例如在其内部操作)测shield()也会取消。

  如果希望完全忽略取消操作 (不推荐) 则 shield() 函数需要配合一个 try/except 代码段,如下所示:

try:
    res = await shield(something())
except CancelledError:
    res = None

  个人注解:不知道怎么示例

  超时

  coroutine asyncio.wait_for(aw, timeout, *, loop=None)

  等待 aw 可等待对象 完成,指定 timeout 秒数后超时。

  如果 aw 是一个协程,它将自动作为任务加入日程。

  timeout 可以为 None,也可以为 float 或 int 型数值表示的等待秒数。如果 timeout 为 None,则等待直到完成。

  如果发生超时,任务将取消并引发 asyncio.TimeoutError.

  要避免任务 取消,可以加上 shield()

  函数将等待直到目标对象确实被取消,所以总等待时间可能超过 timeout 指定的秒数。

  如果等待被取消,则 aw 指定的对象也会被取消。

# 超时 start
import asyncio
async def eternity():
    # Sleep for one hour
    await asyncio.sleep(3600)
    print('yay!')

async def  main():
    # Wait for at most 1 second
    try:
        await asyncio.wait_for(eternity(),timeout=1.0)
    except asyncio.TimeoutError:
        print('timeout')
asyncio.run(main())
# 超时 end

  输出为

timeout

  因为奢侈的超时时间为1秒但是协程执行需要等待3600秒所以触发TimeoutError异常,触发异常后执行except的打印

  简单等待

  coroutine asyncio.wait(aws,*,loop=None,timeout=None,return_when=ALL_COMPLETED)

  并发第运行aws可迭代对象中的可等待对象并进入阻塞状态直到满足return_when所指定的条件。

  返回两个Task/Future集合:(done,pending)

  用法:

done,pending = await asyncio.wait(aws)

  注意:传递参数aws为一个可等待对象的集合,例如{Task1,Task2}返回为一个元组(done,pending)分别为已完成任务的集合和未完成任务集合

  如指定 timeout (float 或 int 类型) 则它将被用于控制返回之前等待的最长秒数。

  请注意此函数不会引发 asyncio.TimeoutError。当超时发生时,未完成的 Future 或 Task 将在指定秒数后被返回。

  return_when 指定此函数应在何时返回。它必须为以下常数之一:

常数

描述

FIRST_COMPLETED

函数将在任意可等待对象结束或取消时返回。

FIRST_EXCEPTION

函数将在任意可等待对象因引发异常而结束时返回。当没有引发任何异常时它就相当于 ALL_COMPLETED

ALL_COMPLETED

函数将在所有可等待对象结束或取消时返回。

  默认参数为ALL_COMPLETED

  与 wait_for() 不同,wait() 在超时发生时不会取消可等待对象。

  3.8 版后已移除: 如果 aws 中的某个可等待对象为协程,它将自动作为任务加入日程。直接向 wait() 传入协程对象已弃用,因为这会导致 令人迷惑的行为

  注解

   wait() 会自动将协程作为任务加入日程,以后将以 (done, pending) 集合形式返回显式创建的任务对象。因此以下代码并不会有预期的行为:

async def foo():
    return 42

async def main():
    # 3.8版本以后传递协程对象的方法已移除
    coro = foo()
    done,pending = await asyncio.wait({coro})
    print(coro)
    # if下代码永远不会运行因为coro是一个协程对象而不是task所以判断永远不成立
    if coro in done:
        pass
asyncio.run(main())  

  以上代码修正方法如下

async def foo():
    return 42

async def main():
    # 3.8版本以后传递协程对象的方法已移除
    coro = foo()
    done,pending = await asyncio.wait({coro})
    print(coro)
    # if下代码永远不会运行因为coro是一个协程对象而不是task所以判断永远不成立
    if coro in done:
        pass
    print(done,pending)
    # {<Task finished name='Task-8' coro=<foo() done, defined at d:/learn-python3/学习脚本/协程系列/协程与任务.py:197> result=42>} set()
    # 创建task传递
    task = asyncio.create_task(foo())
    done,pending = await asyncio.wait({task})
    # 完成一个task,所以done为完成task的集合,pending为空
    print(done,pending)
    if task in done:
        print(1)
asyncio.run(main())

  以上代码只执行一个协程正常完成,下面设置参数return_when在同时运行几个task的时候触发中断,然后可以即返回done的的task也可以返回pending未完成的task

# 以下代码演示返回两种类型的task一种是以完成一种是未完成
import asyncio
import time
# 定义3个协程函数等待时间分别为 2 3 4秒
async def hello1():
    print('hello1 start')
    await asyncio.sleep(2)
    print('hello1 end')

async def hello2():
    print('hello2 start')
    await asyncio.sleep(3)
    print('hello2 end')

async def hello3():
    print('hello3 start')
    await asyncio.sleep(4)
    print('hello3 end')

# 主函数
async def  main():
    # 创建3个task分别执行3个协程
    task1 = asyncio.create_task(hello1())
    task2 = asyncio.create_task(hello2())
    task3 = asyncio.create_task(hello2())
    # 执行,并记录前后时间
    start_time = time.time()
    done,pending = await asyncio.wait({task1,task2,task3},return_when=asyncio.FIRST_COMPLETED)
    end_time = time.time()
    # 打印执行完毕的协程
    for i in done:
        print(i)
    # 打印中断的协程
    for p in pending:
        print(p)
    print(end_time-start_time)

asyncio.run(main())

  期望的输出如下

hello1 start
hello2 start
hello2 start
hello1 end
<Task finished name='Task-8' coro=<hello1() done, defined at d:/learn-python3/学习脚本/协程系列/协程与任务.py:221> result=None>
<Task pending name='Task-9' coro=<hello2() running at d:/learn-python3/学习脚本/协程系列/协程与任务.py:228> wait_for=<Future pending cb=[<TaskWakeupMethWrapper object at 0x0000017846753AC0>()]>>
<Task pending name='Task-10' coro=<hello2() running at d:/learn-python3/学习脚本/协程系列/协程与任务.py:228> wait_for=<Future pending cb=[<TaskWakeupMethWrapper object at 0x0000017846753AF0>()]>>
2.0285181999206543

  分析:3个任务几乎同时执行所以打印了 

hello1 start
hello2 start
hello2 start

  然后由于设置了以下参数,完成一个协程就返回了即第一个hello1因为执行等待的时候最短为2秒所以完成以后就返回了

  此时task2 task3还没有执行完毕

return_when=asyncio.FIRST_COMPLETED

  接下来执行打印完成的task和未完成的task

<Task finished name='Task-8' coro=<hello1() done, defined at d:/learn-python3/学习脚本/协程系列/协程与任务.py:221> result=None>
<Task pending name='Task-9' coro=<hello2() running at d:/learn-python3/学习脚本/协程系列/协程与任务.py:228> wait_for=<Future pending cb=[<TaskWakeupMethWrapper object at 0x0000017846753AC0>()]>>
<Task pending name='Task-10' coro=<hello2() running at d:/learn-python3/学习脚本/协程系列/协程与任务.py:228> wait_for=<Future pending cb=[<TaskWakeupMethWrapper object at 0x0000017846753AF0>()]>>

  最后打印消耗的时间为2秒

2.0285181999206543

  asyncio.as_completed(aws,*,loop=None)

  并发地运行 aws 可迭代对象中的 可等待对象。 返回一个协程的迭代器。 所返回的每个协程可被等待以从剩余的可等待对象的可迭代对象中获得最早的下一个结果。

  如果在所有 Future 对象完成前发生超时则将引发 asyncio.TimeoutError

  示例:

for coro in as_completed(aws):
    earliest_result = await coro
    # ...

  完整示例

# asyncio.as_completed start
async def hello1():
    print("Hello world 01 begin")
    await asyncio.sleep(5)  #大约5秒
    print("Hello again 01 end")
    return '哈哈1'
 

async def hello2():
    print("Hello world 02 begin")
    await asyncio.sleep(3) #大约3秒
    print("Hello again 02 end")
    return '哈哈2'
 

async def hello3():
    print("Hello world 03 begin")
    await asyncio.sleep(4) #大约4秒
    print("Hello again 03 end")
    return '哈哈3'
 
async def main():
    task1 = asyncio.create_task(hello1())
    task2 = asyncio.create_task(hello2())
    task3 = asyncio.create_task(hello3())
    s=asyncio.as_completed({task1,task2,task3})
    start_time = time.time()
    print('start genrator')
    for i in s:
        result = await i
        print(result)
    print('end generator')
    end_time = time.time()
    print('---------------------------------------')
    print(end_time-start_time)
asyncio.run(main())
 
#  asyncio.as_completed end

  期望输出如下

start genrator
Hello world 01 begin
Hello world 02 begin
Hello world 03 begin
Hello again 02 end
哈哈2
Hello again 03 end
哈哈3
Hello again 01 end
哈哈1
end generator
---------------------------------------
5.001317739486694

  

  解析:asyncio.as_completed返回的是一个generator 内部对象为传递task执行的时间顺序返回,执行时间越短的task在最前,执行时间最长的task在最后

  然后循环调用相当于同时执行3个协程,所以先输出

Hello world 01 begin
Hello world 02 begin
Hello world 03 begin

  然后因为hello2() hello3() hello1()的执行时间分别为3 4 5 所以输出的顺序就是

Hello again 02 end
哈哈2
Hello again 03 end
哈哈3
Hello again 01 end
哈哈1

  最后总计执行时间为5秒即最长协程的asyncio.sleep(5)的时间

  Task对象

  一个与 Future 类似 的对象,可运行 Python 协程。非线程安全。

  Task 对象被用来在事件循环中运行协程。如果一个协程在等待一个 Future 对象,Task 对象会挂起该协程的执行并等待该 Future 对象完成。当该 Future 对象 完成,被打包的协程将恢复执行。

  事件循环使用协同日程调度: 一个事件循环每次运行一个 Task 对象。而一个 Task 对象会等待一个 Future 对象完成,该事件循环会运行其他 Task、回调或执行 IO 操作。

  使用高层级的 asyncio.create_task() 函数来创建 Task 对象,也可用低层级的 loop.create_task() 或 ensure_future() 函数。不建议手动实例化 Task 对象。

  要取消一个正在运行的 Task 对象可使用 cancel() 方法。调用此方法将使该 Task 对象抛出一个 CancelledError 异常给打包的协程。如果取消期间一个协程正在等待一个 Future 对象,该 Future 对象也将被取消。

  cancelled() 可被用来检测 Task 对象是否被取消。如果打包的协程没有抑制 CancelledError 异常并且确实被取消,该方法将返回 True

  asyncio.Task 从 Future 继承了其除 Future.set_result() 和 Future.set_exception() 以外的所有 API。

  Task 对象支持 contextvars 模块。当一个 Task 对象被创建,它将复制当前上下文,然后在复制的上下文中运行其协程。

  cancel()

  请求取消 Task 对象。

  这将安排在下一轮事件循环中抛出一个 CancelledError 异常给被封包的协程。

  协程在之后有机会进行清理甚至使用 try ... ... except CancelledError ... finally 代码块抑制异常来拒绝请求。不同于 Future.cancel()Task.cancel() 不保证 Task 会被取消,虽然抑制完全取消并不常见,也很不鼓励这样做。

  以下示例演示了协程是如何侦听取消请求的:

  为了理解协程的执行顺序我们先写一个简单的协程 

  创建task的时候就可以执行对应的协程函数,在遇到await时等待

  示例:

import asyncio
import time
async def hello():
    print('hello')
    await asyncio.sleep(1)
    print('world')

async def main():
    task = asyncio.create_task(hello())
    #await task
asyncio.run(main())

  输出为

hello

  解析:执行主协程main()的时候当执行到,创建task的时候已经开始执行协程函数hello()

task = asyncio.create_task(hello())

  所以先输出hello然后执行到await的时候就阻塞等待了,返回main()继续执行,但是main没有语句了所以执行结束

await asyncio.sleep(1)

  修改代码在main()协程函数加上await

import asyncio
import time
async def hello():
    print('hello')
    await asyncio.sleep(1)
    print('world')

async def main():
    task = asyncio.create_task(hello())
    await task
asyncio.run(main())

  输出如下

hello
world

  解析:前面的执行过程和上述是一样的,只不过在执行到hello()的await的时候线程阻塞等待asyncio.sleep(1)的执行,返回main继续执行遇到await task,在这里等待执行,其实就相当于等待hello()协程内的asyncio.sleep(1),然后因为遇到await程序又回到hello()继续执行打印出world结束。

  演示协程取消请求

# task cancel() start
async def cancel_me():
    print('cancel_me(): before sleep')

    try:
        # Wait for 1 hour
        await asyncio.sleep(3600)
    except asyncio.CancelledError:
        print('cancel_me(): cancel sleep')
        raise
    finally:
        print('cancel_me(): after sleep')

async def main():
    # Create a "cancel_me" Task
    task = asyncio.create_task(cancel_me())

    # Wait for 1 second
    await asyncio.sleep(1)

    task.cancel()
    try:
        await task
    except asyncio.CancelledError:
        print("main(): cancel_me is cancelled now")

asyncio.run(main())
# task cancel() end

  预期输出如下

cancel_me(): before sleep
cancel_me(): cancel sleep
cancel_me(): after sleep
main(): cancel_me is cancelled now

  解析:

  执行主协程main()到以下语句,创建一个task

  这个时候就开始执行cancel_me()协程

task = asyncio.create_task(cancel_me())

  打印出

cancel_me(): before sleep

  还没有遇到await所以继续执行直到

await asyncio.sleep(3600)

  这个时候cancel_me()遇到await阻塞了,等待需要3600秒,线程不等待返回main()继续执行

await asyncio.sleep(1)

  这个时候阻塞返回cancel_me()但是返回后发现还在阻塞,因为main()阻塞是1秒,cancel_me()需要阻塞3600秒,所以接着执行main()下语句

task.cacel()

  取消task

  接下来继续执行try下面的

await task

  又遇到await返回cancel_me()执行,因为取消了task触发了CanceledError所以执行except下的语句

print('cancel_me(): cancel sleep')

  打印了

cancel_me(): cancel sleep

  然后执行raise抛出错误

raise

  继续执行finally语句

print('cancel_me(): after sleep')

  打印

cancel_me(): after sleep

  因为是在try执行的await task抛出了异常所以返回main()执行except下的语句

 print("main(): cancel_me is cancelled now")

  打印了

main(): cancel_me is cancelled now

  使用调试模式分析执行过程

  从执行协程main()开始分析

 

 

 

 

 

 

  cancelled()

如果 Task 对象 被取消 则返回 True

当使用 cancel() 发出取消请求时 Task 会被 取消,其封包的协程将传播被抛入的 CancelledError 异常。

  done()

如果 Task 对象 已完成 则返回 True

当 Task 所封包的协程返回一个值、引发一个异常或 Task 本身被取消时,则会被认为 已完成

  result()

返回 Task 的结果。

如果 Task 对象 已完成,其封包的协程的结果会被返回 (或者当协程引发异常时,该异常会被重新引发。)

如果 Task 对象 被取消,此方法会引发一个 CancelledError 异常。

如果 Task 对象的结果还不可用,此方法会引发一个 InvalidStateError 异常。

  exception()

返回 Task 对象的异常。

如果所封包的协程引发了一个异常,该异常将被返回。如果所封包的协程正常返回则该方法将返回 None

如果 Task 对象 被取消,此方法会引发一个 CancelledError 异常。

如果 Task 对象尚未 完成,此方法将引发一个 InvalidStateError 异常。

  add_done_callback(callback*context=None)

添加一个回调,将在 Task 对象 完成 时被运行。

此方法应该仅在低层级的基于回调的代码中使用。

要了解更多细节请查看 Future.add_done_callback() 的文档。

  remove_done_callback(callback)

从回调列表中移除 callback 。

此方法应该仅在低层级的基于回调的代码中使用。

要了解更多细节请查看 Future.remove_done_callback() 的文档。

  get_stack(*limit=None)

返回此 Task 对象的栈框架列表。

如果所封包的协程未完成,这将返回其挂起所在的栈。如果协程已成功完成或被取消,这将返回一个空列表。如果协程被一个异常终止,这将返回回溯框架列表。

框架总是从按从旧到新排序。

每个被挂起的协程只返回一个栈框架。

可选的 limit 参数指定返回框架的数量上限;默认返回所有框架。返回列表的顺序要看是返回一个栈还是一个回溯:栈返回最新的框架,回溯返回最旧的框架。(这与 traceback 模块的行为保持一致。)

  print_stack(*limit=Nonefile=None)

打印此 Task 对象的栈或回溯。

此方法产生的输出类似于 traceback 模块通过 get_stack() 所获取的框架。

limit 参数会直接传递给 get_stack()

file 参数是输出所写入的 I/O 流;默认情况下输出会写入 sys.stderr

  get_coro()

返回由 Task 包装的协程对象。

3.8 新版功能.

  get_name()

返回 Task 的名称。

如果没有一个 Task 名称被显式地赋值,默认的 asyncio Task 实现会在实例化期间生成一个默认名称。

3.8 新版功能.

  set_name(value)

设置 Task 的名称。

value 参数可以为任意对象,它随后会被转换为字符串。

在默认的 Task 实现中,名称将在任务对象的 repr() 输出中可见。

3.8 新版功能.

  classmethod all_tasks(loop=None)

返回一个事件循环中所有任务的集合。

默认情况下将返回当前事件循环中所有任务。如果 loop 为 None,则会使用 get_event_loop() 函数来获取当前事件循环。

Deprecated since version 3.7, will be removed in version 3.9: 请不要将此方法作为任务方法来调用。 应当改用 asyncio.all_tasks() 函数。

  classmethod current_task(loop=None)

返回当前运行任务或 None

如果 loop 为 None,则会使用 get_event_loop() 函数来获取当前事件循环。

Deprecated since version 3.7, will be removed in version 3.9: 请不要将此方法作为任务方法来调用。 应当改用 asyncio.current_task() 函数。

  

  

原文地址:https://www.cnblogs.com/minseo/p/15425702.html