python3.8异步入门

概述

虽然协程很简单,但是要用好很难,这篇文章主要是梳理python3.8提供的协程方法、

启动

启动协程主要有如下方法:

run-启动单任务

启动单任务

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

create_task-异步执行

该函数主要功能是非阻塞地开启一个协程任务,类比就相当于启动了一个线程。启动多任务,注意,需要在协程单任务里面启动。


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')}")

3.8中也可以给task增加一个任务名称(暂时不知道有卵用)

gather-并发任务

awaitable asyncio.gather(*aws, loop=None, return_exceptions=False)->list
并发 运行 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*:
    await asyncio.gather(
        factorial("A", 2),
        factorial("B", 3),
        factorial("C", 4),
    )

asyncio.run(main())

# Expected output:
#
#     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

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

sleep-休眠

asyncio.sleep(delay, result=None, *, loop=None)
阻塞 delay 指定的秒数。

如果指定了 result,则当协程完成时将其返回给调用者。
sleep() 总是会挂起当前任务,以允许其他任务运行。

3.8版本弃用
以下协程示例运行 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())

结束

shield-防止任务被取消

awaitable asyncio.shield(aw, *, loop=None)
保护一个 可等待对象 防止其被 取消。

如果 aw 是一个协程,它将自动作为任务加入日程。
以下语句:
res = await shield(something())等价:res = await something()
不同之处 在于如果包含它的协程被取消,在 something() 中运行的任务不会被取消。从 something() 的角度看来,取消操作并没有发生。然而其调用者已被取消,因此 “await” 表达式仍然会引发 CancelledError。

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

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

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

wait_for-超时

asyncio.wait_for(aw, timeout, *, loop=None)
等待 aw 可等待对象 完成,指定 timeout 秒数后超时。
timeout 可以为 None,也可以为 float 或 int 型数值表示的等待秒数。如果 timeout 为 None,则等待直到完成。

如果发生超时,任务将取消并引发 asyncio.TimeoutError.
要提示超时但避免避免任务 取消,可以加上 shield()。
如果等待被取消,则 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!

wait-等待

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)
如指定 timeout (float 或 int 类型) 则它将被用于控制返回之前等待的最长秒数。
请注意此函数不会引发 asyncio.TimeoutError。当超时发生时,未完成的 Future 或 Task 将在指定秒数后被返回。

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

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

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

async def foo():
    return 42

task = asyncio.create_task(foo())
done, pending = await asyncio.wait({task})

if task in done:
    # Everything will work as expected now.

线程交互

太复杂暂时不考虑

内省

asyncio.current_task(loop=None)
返回当前运行的 Task 实例,如果没有正在运行的任务则返回 None。
如果 loop 为 None 则会使用 get_running_loop() 获取当前事件循环。
asyncio.all_tasks(loop=None)
返回事件循环所运行的未完成的 Task 对象的集合。
如果 loop 为 None,则会使用 get_running_loop() 获取当前事件循环。

Task对象

class asyncio.Task(coro, *, loop=None, name=None)
一个与 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。

cancel()

请求取消 Task 对象。
这将安排在下一轮事件循环中抛出一个 CancelledError 异常给被封包的协程。
协程在之后有机会进行清理甚至使用 try … … except CancelledError … finally 代码块抑制异常来拒绝请求。不同于 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。

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() 函数来获取当前事件循环。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章