python3.7下的asyncio框架(一)

python3.7下的asyncio框架(一)

基础迭代器执行示意图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u5SaMtMZ-1585215647815)(/Users/bobwang/Library/Application Support/typora-user-images/image-20200325190313155.png)]

一、语法

协程通过 async/await 语法进行声明,是编写异步应用的推荐方式,

import asyncio
async def hello():
    print(f'hello{time.time()}')
    await asyncio.sleep(1)
    print(f'world{time.time()}')
if __name__ == '__main__':
  asyncio.run(hello())
 '''
 hello1585145001.2409651
world1585145002.244873
 '''

二、真正的运行一个协程

asyncio提供了三种方式

1、asyncio.run(函数用来运行最高层级的入口点 “hello()” 函数),等待一个协程。以下代码段会在等待 1 秒后打印 “hello”,然后 再次 等待 2 秒后打印 “world”:(同步阻塞并非异步执行)
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())
'''
start time22:12:56
hello
world
finished at 22:12:59
'''
2、asyncio.create_task() 函数用来并发运行作为 asyncio 任务 的多个协程。

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

async def main():
    print(f"started at {time.strftime('%X')}")
		task1 = asyncio.create_task(
        say_after(1, 'hello'))

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

    print(f"finished at {time.strftime('%X')}")
    '''
    start time22:26:39
hello
world
finished at 22:26:41
    '''
    
其实以上的例子也可以写成这样
async def main():

    print(f"start time{time.strftime('%X')}")
    asyncio.create_task(
        say_after(1, 'hello'))

    task2 = asyncio.create_task(
        say_after(2, 'world'))
    # await task1
    await task2
    print(f"finished at {time.strftime('%X')}")
    '''
    start time22:29:04
hello
world
finished at 22:29:06
同样耗时2秒,思考原因,使用await就相当于阻塞,等待结果,语法上只要使用了asyncio.create_task()就已经将协程交给事件循环loop执行了,如果没有await,就拿不到执行结果,程序相当于就是异步执行完就直接向下执行了
    '''
3、可等待对象

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

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

a 、协程
import asyncio

async def nested():
    return 42

async def main():
    # Nothing happens if we just call "nested()".
    # A coroutine object is created but not awaited,
    # so it *won't run at all*.
    # RuntimeWarning: coroutine 'nested' was never awaited
    nested()

    # Let's do it differently now and await it:
    print(await nested())  # will print "42".

asyncio.run(main())
重要

在本文档中 “协程” 可用来表示两个紧密关联的概念:

  • 协程函数: 定义形式为 async def 的函数;
  • 协程对象: 调用 协程函数 所返回的对象。
b、任务

任务 被用来设置schedule(调度器)以便 并发 执行协程。

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

import asyncio
async def nested():
    return 42

async def main():
    # Schedule nested() to run soon concurrently with "main()".
    task = asyncio.create_task(nested())

    # "task" can now be used to cancel "nested()", or can simply be awaited to wait until it is complete:
    await task
    print(task.result()) # will print "42"

asyncio.run(main())
c、Future

Future是一种特殊的低层级的可等待对象,表示一个异步操作的最终结果,完全可以用task任务替代

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

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

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

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

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

三、运行asynio程序

1、asyncio.``run(coro, ***, debug=False) 此函数在3.7才加入asynio模块,处于暂定基准状态

执行 coroutine coro 并返回结果。

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

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

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

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

该函数替代了在python3.6时的

loop = asyncio.get_event_loop()
loop.run_until_complete(coro)
loop.close()

例子:

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

asyncio.run(main())
2、创建任务

asyncio.``create_task(coro)

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

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

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

async def coro():
    ...

# In Python 3.7+
task = asyncio.create_task(coro())
...

# This works in all Python versions but is less readable
task = asyncio.ensure_future(coro())
...
3、sleep

coroutine asyncio.``sleep(delay, result=None, ***, loop=None)

阻塞 delay 指定的秒数。

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

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

loop 参数已弃用,计划在 Python 3.10 中移除。

以下协程示例运行 5 秒,每秒显示一次当前日期:

import asyncio
import datetime

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

asyncio.run(display_date())
4、并发执行任务

awaitable asyncio.``gather(*aws, loop=None, return_exceptions=False)

并发执行aws序列中的可等待对象(协程,任务,Future)

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

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

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

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

如果 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*:
    await asyncio.gather(
        factorial("A", 2),
        factorial("B", 3),
        factorial("C", 4),
    )

asyncio.run(func())
'''
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

'''
可以看到ABC三个任务同时进行计算,通过await asyncio.sleep(1) 让出计算资源
5、屏蔽取消操作

awaitable asyncio.``shield(aw, ***, loop=None)

保护一个 可等待对象 防止其被 取消

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

以下语句:

res = await shield(something())

相当于:

res = await something()

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

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

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

from asyncio import shield
try:
    res = await shield(something())
except CancelledError:
    res = None
6、超时

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

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

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

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

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

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

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

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

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

# Expected output:
#
#     timeout!
7、简单等待(waiting primitives)

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

并发运行 aws 指定的 可等待对象 并阻塞线程直到满足 return_when 指定的条件。

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

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

用法:

done, pending = await asyncio.wait(aws)

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

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

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

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

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

async def foo():
    await asyncio.sleep(1)
    return 42


async def fun23():
    task = asyncio.create_task(foo())
    done, pending = await asyncio.wait({task},timeout=0.1)

    if task in done:
        print('ok')
    else:
        print('wrong')
        
asyncio.run(fun23())
'''
超时未完成输出 wrang
如果将timeout=None,则输出ok
'''

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

并发地运行 aws 集合中的 可等待对象。返回一个 Future 对象的迭代器。返回的每个 Future 对象代表来自剩余可等待对象集合的最早结果。

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

示例:

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}")
    return f
  
  
async def fun():
    try:
        for f in asyncio.as_completed([factorial("A", 2), factorial("B", 3)], timeout=1):
            earliest_result = await f
            print(f)
            print(earliest_result)
    except asyncio.TimeoutError:
        print('timeout')
        
 asyncio.run(fun())
'''
Task A: Compute factorial(2)...
Task B: Compute factorial(2)...
timeout 
Task A: factorial(2) = 2
Task B: Compute factorial(3)...
'''

四、Task对象

Task对象是一个与future类似的对象,可运行python协程,非线性安全

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

事件循环使用协同schedule调度:一个事件循环每次运行一个Task对象,而task对象会等待一个Future对象完成该事件循环会运行其他Task、回调或执行IO操作,也就是说程序不会阻塞等待结果

1、Task的创建

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

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

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

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

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

在 3.7 版更改: 加入对 contextvars 模块的支持

2、API
cancel()

请求取消 Task 对象。

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

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

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

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

# Expected output:
#
#     cancel_me(): before sleep
#     cancel_me(): cancel sleep
#     cancel_me(): after sleep
#     main(): cancel_me is cancelled now
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=None, file=None)

打印此 Task 对象的栈或回溯。此方法产生的输出类似于 traceback 模块通过 get_stack() 所获取的框架。limit 参数会直接传递给 get_stack()file 参数是输出所写入的 I/O 流;默认情况下输出会写入 sys.stderr

classmethod all_tasks(loop=None)

返回一个事件循环中所有任务的集合。默认情况下将返回当前事件循环中所有任务。如果 loopNone,则会使用 get_event_loop() 函数来获取当前事件循环。此方法 已弃用 并将在 Python 3.9 中移除。请改用 asyncio.all_tasks() 函数。

classmethod current_task(loop=None)

返回当前运行任务或 None。如果 loopNone,则会使用 get_event_loop() 函数来获取当前事件循环。此方法 已弃用 并将在 Python 3.9 中移除。请改用 asyncio.current_task() 函数。

五、基于生成器的协程

注意:对基于生成器的协程的支持 已弃用 并计划在 Python 3.10 中移除。

基于生成器的协程是 async/await 语法的前身。它们是使用 yield from 语句创建的 Python 生成器,可以等待 Future 和其他协程。

基于生成器的协程应该使用 @asyncio.coroutine 装饰,虽然这并非强制。

@asyncio.coroutine`

用来标记基于生成器的协程的装饰器。此装饰器使得旧式的基于生成器的协程能与 async/await 代码相兼容:

@asyncio.coroutine
def old_coro():
    print('ok')
    yield from asyncio.sleep(1)


async def func12():
    await old_coro()


if __name__ == '__main__':
    if asyncio.iscoroutine(old_coro()): #如果 obj 是一个 协程对象 则返回 True。此方法不同于 inspect.iscoroutine() 因为它对基于生成器的协程返回 True
        asyncio.run(func12())
    if asyncio.iscoroutinefunction(old_coro): #如果 func 是一个 协程函数 则返回 True。此方法不同于 inspect.iscoroutinefunction() 因为它对以 @coroutine 装饰的基于生成器的协程函数返回 True。
      
        print('我是协程')
'''
ok
我是协程
'''

此装饰器不应该被用于 async def 协程。

此装饰器 已弃用 并计划觉得 Python 3.10 中移除。

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值