# 协程与任务

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

## 协程

源码： Lib/asyncio/coroutines.py

通过 async/await 语法来声明 协程 是编写 asyncio 应用的推荐方式。例如，以下代码段会打印 "hello"，等待 1 秒，再打印 "world":

    >>> import asyncio

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

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

注意：简单地调用一个协程并不会使其被调度执行:

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

要实际运行一个协程，asyncio 提供了以下几种机制：

- `asyncio.run()` 函数用来运行最高层级的入口点 "main()" 函数 (参见上面的示例。)

- 对协程执行 await。以下代码段会在等待 1 秒后打印 "hello"，然后 再次 等待 2 秒后打印 "world":

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

  预期的输出:

      started at 17:13:52
      hello
      world
      finished at 17:13:55

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

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

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

          # 等待直到两个任务都完成
          # （会花费约 2 秒钟。）
          await task1
          await task2

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

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

      started at 17:14:32
      hello
      world
      finished at 17:14:34

- `asyncio.TaskGroup` 类提供了 `create_task()` 的更现代化的替代。使用此 API，之前的例子将变为:

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

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

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

          # 当上下文管理器退出时 await 是隐式执行的。

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

  用时和输出结果应当与之前的版本相同。

  Added in version 3.11: `asyncio.TaskGroup`.

## 可等待对象

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

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

协程

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

    import asyncio

    async def nested():
        return 42

    async def main():
        # 如果我们只调用 "nested()" 则无事发生。
        # 一个协程对象被创建但是没有被等待，
        # 因此它 *根本不会运行*。
        nested()  # 将引发 "RuntimeWarning"。

        # 现在让我们等待它：
        print(await nested())  # 将打印 "42"。

    asyncio.run(main())

重要

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

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

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

任务

任务 被用来 并发地 调度协程。

当一个协程被 `asyncio.create_task()` 等函数封装为一个 任务 时，该协程会被自动调度执行:

    import asyncio

    async def nested():
        return 42

    async def main():
        # 调度 nested() 与 "main()" 并发运行。
        task = asyncio.create_task(nested())

        # 现在可以通过 "task" 来取消 "nested()"，
        # 也可以等待 "task" 直到它被完成：
        await task

    asyncio.run(main())

Futures

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

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

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

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

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

    async def main():
        await function_that_returns_a_future_object()

        # 这样也可以：
        await asyncio.gather(
            function_that_returns_a_future_object(),
            some_python_coroutine()
        )

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

## 创建任务

源码： Lib/asyncio/tasks.py

asyncio.create_task(coro, *, name=None, context=None, eager_start=None, **kwargs)

  将 coro 协程 封装为一个 `Task` 并调度其执行。返回 Task 对象。

  完整函数签名与 `Task` 构造器（或工厂函数）的大致相同 —— 所有传给此函数的关键字参数都会被传给该接口。

  可选的 context 参数允许指定自定义的 `contextvars.Context` 供 coro 运行。当未提供 context 时将创建当前上下文的副本。

  一个可选的仅限关键字的 eager_start 参数允许指定任务是应该在调用 create_task 期间主动执行，还是应稍后被调度。如果没有传递 eager_start，则会使用 `loop.set_task_factory()` 设置的模式。

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

  备注

  `asyncio.TaskGroup.create_task()` 是一个平衡了结构化并发的新选择；它允许等待一组相关任务并具有极强的安全保证。

  重要

  保存一个指向此函数的结果的引用，以避免任务在执行过程中消失。事件循环将只保留对任务的弱引用。 未在其他地方被引用的任务可能在任何时候被作为垃圾回收，即使是在它被完成之前。如果需要可靠的“发射后不用管”后台任务，请将它们放到一个多项集中:

      background_tasks = set()

      for i in range(10):
          task = asyncio.create_task(some_coro(param=i))

          # 将任务加入集合。这将创建一个强引用。
          background_tasks.add(task)

          # 为避免永远保留对已结束任务的引用，
          # 让每个任务在完成后将对自己的引用
          # 移出集合：
          task.add_done_callback(background_tasks.discard)

  Added in version 3.7.

  在 3.8 版本发生变更: 增加了 name 形参。

  在 3.11 版本发生变更: 增加了 context 形参。

  在 3.14 版本发生变更: 通过传递所有的 kwargs 添加了 eager_start 形参。

## 任务取消

任务可以便捷和安全地取消。当任务被取消时，`asyncio.CancelledError` 将在遇到机会时在任务中被引发。

推荐协程使用 `try/finally` 代码块来可靠地执行清理逻辑。对于 `asyncio.CancelledError` 被显式捕获的情况，它通常应当在清理完成时被传播。 `asyncio.CancelledError` 会直接子类化 `BaseException` 因此大多数代码都不需要关心这一点。

启用结构化并发的 asyncio 组件，如 `asyncio.TaskGroup` 和 `asyncio.timeout()`，在内部是使用撤销操作来实现的因而在协程屏蔽了 `asyncio.CancelledError` 时可能无法正常工作。类似地，用户代码通常也不应调用 `uncancel`。 但是，在确实想要屏蔽 `asyncio.CancelledError` 的情况下，则还有必要调用 `uncancel()` 来完全移除撤销状态。

## 任务组

任务组合并了一套用于等待分组中所有任务完成的方便可靠方式的任务创建 API。

class asyncio.TaskGroup

  持有一个任务分组的 异步上下文管理器。可以使用 `create_task()` 将任务添加到分组中。当该上下文管理器退出时所有任务都将被等待。

  Added in version 3.11.

  create_task(coro, *, name=None, context=None, eager_start=None, **kwargs)

    在该任务组中创建一个任务。签名与 `asyncio.create_task()` 的签名相匹配。 如果该任务组未激活（例如尚未进入、已经结束或在关闭过程中），我们将关闭所给出的 `coro`。

    在 3.13 版本发生变更: 如果任务组未激活则关闭所给出的协程。

    在 3.14 版本发生变更: 将所有的 kwargs 传递给 `loop.create_task()`

  cancel()

    Cancel the task group. This is a non-exceptional, early exit of the task group's lifetime -- useful once the group's goal has been met or its services no longer needed.

    `cancel()` will be called on any tasks in the group that aren't yet done, as well as the parent (body) of the group. The task group context manager will exit without `asyncio.CancelledError` being raised.

    If `cancel()` is called before entering the task group, the group will be cancelled upon entry. This is useful for patterns where one piece of code passes an unused `asyncio.TaskGroup` instance to another in order to have the ability to cancel anything run within the group.

    `cancel()` is idempotent and may be called after the task group has already exited.

    Some ways to use `cancel()`:

    - call it from the task group body based on some condition or event

    - pass the task group instance to child tasks via `create_task()`, allowing a child task to conditionally cancel the entire entire group

    - 在打开任务组 之前 传入任务组实例或将 `cancel()` 方法绑定到其他任务，允许远程撤销。

    Added in version 3.15.

示例:

    async def main():
        async with asyncio.TaskGroup() as tg:
            task1 = tg.create_task(some_coro(...))
            task2 = tg.create_task(another_coro(...))
        print(f"Both tasks have completed now: {task1.result()}, {task2.result()}")

The `async with` statement will wait for all tasks in the group to finish. While waiting, new tasks may still be added to the group (for example, by passing `tg` into one of the coroutines and calling `tg.create_task()` in that coroutine). There is also opportunity to request termination of the entire task group with `tg.cancel()`, based on some condition. Once the last task has finished and the `async with` block is exited, no new tasks may be added to the group.

当首次有任何属于分组的任务因 `asyncio.CancelledError` 以外的异常而失败时，分组中的剩余任务将被取消。 在此之后将无法添加更多任务到该分组中。在这种情况下，如果 `async with` 语句体仍然为激活状态（即 `__aexit__()` 尚未被调用），则直接包含 `async with` 语句的任务也会被取消。结果 `asyncio.CancelledError` 将中断一个 `await`，但它将不会跳出包含的 `async with` 语句。

一旦所有任务被完成，如果有任何任务因 `asyncio.CancelledError` 以外的异常而失败，这些异常会被组合在 `ExceptionGroup` 或 `BaseExceptionGroup` 中（选择其中较适合的一个；参见其文档）并将随后引发。

两个基础异常会被特别对待：如果有任何任务因 `KeyboardInterrupt` 或 `SystemExit` 而失败，任务分组仍然会取消剩余的任务并等待它们，但随后初始 `KeyboardInterrupt` 或 `SystemExit` 而不是 `ExceptionGroup` 或 `BaseExceptionGroup` 会被重新引发。

如果 `async with` 语句体因异常而退出（这样将调用 `__aexit__()` 并附带一个异常），此种情况会与有任务失败时一样对待：剩余任务将被取消然后被等待，而非取消类异常会被加入到一个异常分组并被引发。传入到 `__aexit__()` 的异常，除了 `asyncio.CancelledError` 以外，也都会被包括在该异常分组中。同样的特殊对待也适用于上一段所说的 `KeyboardInterrupt` 和 `SystemExit`.

对于任务组应当注意不要将用于“唤醒”其 `__aexit__()` 的内部取消请求与其他地方对其运行的任务提出的取消请求相混淆。 具体来说，当一个任务组在语法上嵌套于另一个任务组中，而两个任务组的某个子任务同时发生异常时，内层的任务组将处理其异常，然后外层的任务组将收到另一个取消请求并处理它自己的异常。

对于任务组在外部被取消同时必须引发 `ExceptionGroup` 的情况，它将调用父任务的 `cancel()` 方法。这样可以确保 `asyncio.CancelledError` 会在下一次 `await` 时被引发，因此取消操作不会丢失。

任务组将保留 `asyncio.Task.cancelling()` 所报告的取消次数。

在 3.13 版本发生变更: 改进了同时处理内部和外部取消操作以及正确保留取消计数的功能。

## 休眠

async asyncio.sleep(delay, result=None)

  阻塞 delay 指定的秒数。

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

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

  将 delay 设为 0 将提供一个经优化的路径以允许其他任务运行。这可供长期间运行的函数使用以避免在函数调用的全过程中阻塞事件循环。

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

      import asyncio
      import datetime as dt

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

      asyncio.run(display_date())

  在 3.10 版本发生变更: 移除了 loop 形参。

  在 3.13 版本发生变更: 如果 delay 为 `nan` 则会引发 `ValueError`。

## 并发地运行任务

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

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

  如果 aws 中的某个可等待对象为协程，它将自动被作为一个任务调度。

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

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

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

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

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

  备注

  一个创建、并发地运行任务并等待它们完成的新选择是 `asyncio.TaskGroup`。 TaskGroup 针对嵌套子任务的调度提供比 gather 更强的安全保证：如果一个任务（或子任务，即由某个任务调度的任务）引发了异常，TaskGroup 将取消剩余的已排期任务，而 gather 不会这样做。

  示例:

      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():
          # *并发地* 调度这三次调用：
          L = await asyncio.gather(
              factorial("A", 2),
              factorial("B", 3),
              factorial("C", 4),
          )
          print(L)

      asyncio.run(main())

      # 预期的输出：
      #
      #     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]

  备注

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

  在 3.7 版本发生变更: 如果 gather 本身被取消，则无论 return_exceptions 取值为何，取消都会被传播。

  在 3.10 版本发生变更: 移除了 loop 形参。

  自 3.10 版本弃用: 如果未提供位置参数或者并非所有位置参数均为 Future 类对象并且没有正在运行的事件循环则会发出弃用警告。

## 主动任务工厂

asyncio.eager_task_factory(loop, coro, *, name=None, context=None)

  用于主动任务执行的任务工厂

  当使用这个工厂函数时 (通过 `loop.set_task_factory(asyncio.eager_task_factory)`)，协程将在 `Task` 被构造时同步开始执行。仅当任务面临阻塞时，它们才会被调度到事件循环上。这将避免可同步完成的协程被调度到事件循环所带来的开销，从而可以达成性能提升。

  此特性会带来好处的一个常见例子是应用了缓存或记忆功能以便在可能的情况避免实际 I/O 的协程。

  备注

  协程的就地执行是一项语义改变。如果协程返回或引发异常，任务将永远不会被调度到事件循环上。如果协程的执行发生阻塞，任务将会被调度到事件循环上。这项改变可能会向现有应用程序引入行为变化。例如，应用程序的任务执行顺序可能会发生改变。

  Added in version 3.12.

asyncio.create_eager_task_factory(custom_task_constructor)

  创建一个主动型任务工厂，类似于 `eager_task_factory()`，在创建新任务时使用所提供的 custom_task_constructor 而不是默认的 `Task`。

  custom_task_constructor 必须是一个 可调用对象，其签名与 `Task.__init__` 的签名相匹配。该可调用对象必须返回一个兼容 `asyncio.Task` 的对象。

  此函数返回一个 可调用对象，将通过 `loop.set_task_factory(factory)`) 被用作一个事件循环的任务工厂。

  Added in version 3.12.

## 屏蔽取消操作

awaitable asyncio.shield(aw)

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

  如果 aw 是一个协程，它将自动被作为任务调度。

  以下语句:

      task = asyncio.create_task(something())
      res = await shield(task)

  相当于:

      res = await something()

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

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

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

      task = asyncio.create_task(something())
      try:
          res = await shield(task)
      except CancelledError:
          res = None

  重要

  保存一个传给此函数的任务的引用，以避免任务在执行过程中消失。事件循环将只保留对任务的弱引用。 未在其他地方被引用的任务可能在任何时候被作为垃圾回收，即使是在它被完成之前。

  在 3.10 版本发生变更: 移除了 loop 形参。

  自 3.10 版本弃用: 如果 aw 不是 Future 类对象并且没有正在运行的事件循环则会发出弃用警告。

## 超时

asyncio.timeout(delay)

  返回一个可被用于限制等待某个操作所耗费时间的 异步上下文管理器。

  delay 可以为 `None`，或是一个表示等待秒数的浮点数/整数。如果 delay 为 `None`，将不会应用时间限制；如果当创建上下文管理器时无法确定延时则此设置将很适用。

  在两种情况下，该上下文管理器都可以在创建之后使用 `Timeout.reschedule()` 来重新安排计划。

  示例:

      async def main():
          async with asyncio.timeout(10):
              await long_running_task()

  如果 `long_running_task` 耗费 10 秒以上完成，该上下文管理器将取消当前任务并在内部处理所引发的 `asyncio.CancelledError`，将其转化为可被捕获和处理的 `TimeoutError`。

  备注

  `asyncio.timeout()` 上下文管理器负责将 `asyncio.CancelledError` 转化为 `TimeoutError`，这意味着 `TimeoutError` 只能在该上下文管理器 之外 被捕获。

  捕获 `TimeoutError` 的示例:

      async def main():
          try:
              async with asyncio.timeout(10):
                  await long_running_task()
          except TimeoutError:
              print("The long operation timed out, but we've handled it.")

          print("This statement will run regardless.")

  `asyncio.timeout()` 所产生的上下文管理器可以被重新调整到不同的终止点并执行检查。

  class asyncio.Timeout(when)

    一个用于撤销已过期协程的 异步上下文管理器。

    建议使用 `asyncio.timeout()` 或 `asyncio.timeout_at()` 而不是直接实例化 `Timeout`。

    `when` 应当是一个指明上下文将要过期的绝对时间，由事件循环的时钟来计时。

    - 如果 `when` 为 `None`，则超时将永远不会被触发。

    - 如果 `when < loop.time()`，则超时将在事件循环的下一次迭代中被触发。

    > when() → float | None

    >   返回当前终止点，或者如果未设置当前终止点则返回 `None`。

    > reschedule(when: float | None)

    >   重新安排超时。

    > expired() → bool

    >   返回上下文管理器是否已超出时限（过期）。

  示例:

      async def main():
          try:
              # 当开始时我们并不知道超时值，所以我们传入 ``None``。
              async with asyncio.timeout(None) as cm:
                  # 现在我们知道超时值了，所以我们将它重新加入计划任务。
                  new_deadline = get_running_loop().time() + 10
                  cm.reschedule(new_deadline)

                  await long_running_task()
          except TimeoutError:
              pass

          if cm.expired():
              print("Looks like we haven't finished on time.")

  超时上下文管理器可以被安全地嵌套。

  Added in version 3.11.

asyncio.timeout_at(when)

  类似于 `asyncio.timeout()`，不同之处在于 when 是停止等待的绝对时间，或者为 `None`。

  示例:

      async def main():
          loop = get_running_loop()
          deadline = loop.time() + 20
          try:
              async with asyncio.timeout_at(deadline):
                  await long_running_task()
          except TimeoutError:
              print("The long operation timed out, but we've handled it.")

          print("This statement will run regardless.")

  Added in version 3.11.

async asyncio.wait_for(aw, timeout)

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

  如果 aw 是一个协程，它将自动被作为任务调度。

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

  如果发生超时，将取消任务并引发 `TimeoutError`。

  要避免任务 `取消`，可以加上 `shield()`。

  此函数将等待直到 Future 确实被取消，所以总等待时间可能超过 timeout。如果在取消期间发生了异常，异常将会被传播。

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

  示例:

      async def eternity():
          # 休眠一小时
          await asyncio.sleep(3600)
          print('yay!')

      async def main():
          # 等待至多 1 秒
          try:
              await asyncio.wait_for(eternity(), timeout=1.0)
          except TimeoutError:
              print('timeout!')

      asyncio.run(main())

      # 预期的输出：
      #
      #     timeout!

  在 3.7 版本发生变更: 当 aw 由于超时被取消时，`wait_for` 会等待 aw 被取消。在之前版本中，它会立即引发 `TimeoutError`.

  在 3.10 版本发生变更: 移除了 loop 形参。

  在 3.11 版本发生变更: 引发 `TimeoutError` 而不是 `asyncio.TimeoutError`。

## 等待原语

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

  并发地运行 aws 可迭代对象中的 `Future` 和 `Task` 实例并进入阻塞状态直到满足 return_when 所指定的条件。

  aws 可迭代对象必须不为空。

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

  用法:

      done, pending = await asyncio.wait(aws)

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

  请注意此函数不会引发 `TimeoutError`。当超时发生时尚未完成的 Future 或 Task 会在设定的秒数后被直接返回。

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

  常量 | 描述
  --- | ---

  asyncio.FIRST_COMPLETED

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

  asyncio.FIRST_EXCEPTION

  该函数将在任何 future 对象由于引发异常而结束时返回。如果没有任何 future 对象引发异常，则其等价于 `ALL_COMPLETED`.

  asyncio.ALL_COMPLETED

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

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

  在 3.10 版本发生变更: 移除了 loop 形参。

  在 3.11 版本发生变更: 直接向 `wait()` 传入协程对象是被禁止的。

  在 3.12 版本发生变更: 增加了对产生任务的生成器的支持。

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

  并发地运行 aws 可迭代对象中的 可等待对象。 返回的对象可以被迭代以获取可等待对象结束时的结果。

  由 `as_completed()` 返回的对象可作为 asynchronous iterator 或普通的 iterator 被迭代。当使用异步迭代时，原来提供的可等待对象如果为 Task 或 Future 对象则会被产出。 这样可以更容易地将之前加入计划的任务与其结果进行对应。例如:

      ipv4_connect = create_task(open_connection("127.0.0.1", 80))
      ipv6_connect = create_task(open_connection("::1", 80))
      tasks = [ipv4_connect, ipv6_connect]

      async for earliest_connect in as_completed(tasks):
          # earliest_connect 已完成。要获取结果可通过等待它
          # 或是调用 earliest_connect.result()
          reader, writer = await earliest_connect

          if earliest_connect is ipv6_connect:
              print("IPv6 connection established.")
          else:
              print("IPv4 connection established.")

  在异步迭代期间，将为不属于 Task 或 Future 对象的可等待对象产出隐式创建的任务。

  当被用作普通的迭代器时，每次迭代将产出一个返回结果的新协程或是引发下一个完成的等待对象对应的异常。此模式将与 Python 3.13 之前的版本保持兼容:

      ipv4_connect = create_task(open_connection("127.0.0.1", 80))
      ipv6_connect = create_task(open_connection("::1", 80))
      tasks = [ipv4_connect, ipv6_connect]

      for next_connect in as_completed(tasks):
          # next_connect 不是原始 Task 对象之一。
          # 它必须被等待以获取结果值或是引发
          # 接下来要结束的可等待对象的异常。
          reader, writer = await next_connect

  如果在所有可等待对象完成之前达到超时限制则会引发 `TimeoutError`。这将在异步迭代期间由 `async for` 循环引发或是在普通迭代期间由所产出的协程引发。

  在 3.10 版本发生变更: 移除了 loop 形参。

  自 3.10 版本弃用: 如果 aws 可迭代对象中的可等待对象不全为 Future 类对象并且没有正在运行的事件循环则会发出弃用警告。

  在 3.12 版本发生变更: 增加了对产生任务的生成器的支持。

  在 3.13 版本发生变更: 该结果现在可被用作 asynchronous iterator 或是普通的 iterator (在之前它只是普通的迭代器)。

## 在线程中运行

async asyncio.to_thread(func, /, *args, **kwargs)

  在不同的线程中异步地运行函数 func。

  向此函数提供的任何 *args 和 **kwargs 会被直接传给 func。并且，当前 `contextvars.Context` 会被传播，允许在不同的线程中访问来自事件循环的上下文变量。

  返回一个可被等待以获取 func 的最终结果的协程。

  这个协程函数主要是用于执行在其他情况下会阻塞事件循环的 IO 密集型函数/方法。例如:

      def blocking_io():
          print(f"start blocking_io at {time.strftime('%X')}")
          # 请注意 time.sleep() 可被替换为任意一种
          # 阻塞式 IO 密集型操作，例如文件操作。
          time.sleep(1)
          print(f"blocking_io complete at {time.strftime('%X')}")

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

          await asyncio.gather(
              asyncio.to_thread(blocking_io),
              asyncio.sleep(1))

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

      asyncio.run(main())

      # 预期的输出：
      #
      # started main at 19:50:53
      # start blocking_io at 19:50:53
      # blocking_io complete at 19:50:54
      # finished main at 19:50:54

  在任何协程中直接调用 `blocking_io()` 将会在调用期间阻塞事件循环，导致额外的 1 秒运行时间。但是，通过改用 `asyncio.to_thread()`，我们可以在单独的线程中运行它从而不会阻塞事件循环。

  备注

  由于 GIL 的存在，`asyncio.to_thread()` 通常只能被用来将 IO 密集型函数变为非阻塞的。但是，对于会释放 GIL 的扩展模块或无此限制的替代性 Python 实现来说，`asyncio.to_thread()` 也可被用于 CPU 密集型函数。

  Added in version 3.9.

## 跨线程调度

asyncio.run_coroutine_threadsafe(coro, loop)

  向指定事件循环提交一个协程。（线程安全）

  返回一个 `concurrent.futures.Future` 以等待来自其他 OS 线程的结果。

  此函数应该从另一个 OS 线程中调用，而非事件循环运行所在线程。示例:

      def in_thread(loop: asyncio.AbstractEventLoop) -> None:
          # 运行一些阻塞 IO
          pathlib.Path("example.txt").write_text("hello world", encoding="utf8")

          # 创建一个协程
          coro = asyncio.sleep(1, result=3)

          # 将协程提交给给定的循环
          future = asyncio.run_coroutine_threadsafe(coro, loop)

          # 使用可选的 timeout 参数等待结果
          assert future.result(timeout=2) == 3

      async def amain() -> None:
          # 获取正在运行的循环
          loop = asyncio.get_running_loop()

          # 在线程中运行一些东西
          await asyncio.to_thread(in_thread, loop)

  也有可能反过来运行。例如:

      @contextlib.contextmanager
      def loop_in_thread() -> Generator[asyncio.AbstractEventLoop]:
          loop_fut = concurrent.futures.Future[asyncio.AbstractEventLoop]()
          stop_event = asyncio.Event()

          async def main() -> None:
              loop_fut.set_result(asyncio.get_running_loop())
              await stop_event.wait()

          with concurrent.futures.ThreadPoolExecutor(1) as tpe:
              complete_fut = tpe.submit(asyncio.run, main())
              for fut in concurrent.futures.as_completed((loop_fut, complete_fut)):
                  if fut is loop_fut:
                      loop = loop_fut.result()
                      try:
                          yield loop
                      finally:
                          loop.call_soon_threadsafe(stop_event.set)
                  else:
                      fut.result()

      # 在另一线程中创建循环
      with loop_in_thread() as loop:
          # 创建一个协程
          coro = asyncio.sleep(1, result=3)

          # 将协程提交至给定的循环
          future = asyncio.run_coroutine_threadsafe(coro, loop)

          # 附带可选的超时参数等待结果
          assert future.result(timeout=2) == 3

  如果在协程内产生了异常，将会通知返回的 Future 对象。它也可被用来取消事件循环中的任务:

      try:
          result = future.result(timeout)
      except TimeoutError:
          print('The coroutine took too long, cancelling the task...')
          future.cancel()
      except Exception as exc:
          print(f'The coroutine raised an exception: {exc!r}')
      else:
          print(f'The coroutine returned: {result!r}')

  参见 concurrency and multithreading 部分的文档。

  不同于其他 asyncio 函数，此函数要求显式地传入 loop 参数。

  Added in version 3.5.1.

## 内省

asyncio.current_task(loop=None)

  返回当前运行的 `Task` 实例，如果没有正在运行的任务则返回 `None`。

  如果 loop 为 `None` 则会使用 `get_running_loop()` 获取当前事件循环。

  Added in version 3.7.

asyncio.all_tasks(loop=None)

  返回事件循环所运行的未完成的 `Task` 对象的集合。

  如果 loop 为 `None`，则会使用 `get_running_loop()` 获取当前事件循环。

  Added in version 3.7.

asyncio.iscoroutine(obj)

  如果 obj 是一个协程对象则返回 `True`。

  Added in version 3.4.

## Task 对象

class asyncio.Task(coro, *, loop=None, name=None, context=None, eager_start=False)

  一个运行 Python 协程 的 `Future-like` 对象。 非线程安全。

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

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

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

  To cancel a running Task use the `cancel()` method. Calling it will cause the Task to throw a `CancelledError` exception into the wrapped coroutine. If a coroutine is awaiting on a future-like object during cancellation, the awaited object will be cancelled.

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

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

  可选的仅限关键字参数 context 允许指定自定义的 `contextvars.Context` 供 coro 运行。 如果未提供 context，Task 将拷贝当前上下文并随后在拷贝的上下文中运行其协程。

  可选的仅限关键字参数 eager_start 允许在任务创建时主动开始 `asyncio.Task` 的执行。如果设为 `True` 并且事件循环正在运行，任务将立即开始执行协程，直到该协程第一次阻塞。 如果协程未发生阻塞即返回或引发异常，任务将主动结束并将跳过向事件循环添加计划任务。

  在 3.7 版本发生变更: 加入对 `contextvars` 模块的支持。

  在 3.8 版本发生变更: 增加了 name 形参。

  自 3.10 版本弃用: 如果未指定 loop 并且没有正在运行的事件循环则会发出弃用警告。

  在 3.11 版本发生变更: 增加了 context 形参。

  在 3.12 版本发生变更: 增加了 eager_start 形参。

  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.stdout`。

  get_coro()

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

    备注

    这对于已经主动完成的任务将返回 `None`。参见 主动任务工厂。

    Added in version 3.8.

    在 3.12 版本发生变更: 新增加的主动任务执行意味着结果可能为 `None`。

  get_context()

    返回关联到该任务的 `contextvars.Context` 对象。

    Added in version 3.12.

  get_name()

    返回 Task 的名称。

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

    Added in version 3.8.

  set_name(value)

    设置 Task 的名称。

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

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

    Added in version 3.8.

  cancel(msg=None)

    请求取消 Task 对象。

    如果该 Task 已经 完成 或 取消，则返回 `False`，否则，将返回 `True`。

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

    协程随后将有机会进行清理甚至通过 `try` ... ... `except CancelledError` ... `finally` 代码块抑制异常来拒绝请求。因此，不同于 `Future.cancel()`, `Task.cancel()` 不保证 Task 会被取消，虽然完全抑制撤销并不常见也很不建议这样做。 但是如果协程决定要抑制撤销，那么它需要在捕获异常之外还调用 `Task.uncancel()`。

    If the Task being cancelled is currently awaiting on a future-like object, that awaited object will also be cancelled. This cancellation propagates down the entire chain of awaited objects.

    在 3.9 版本发生变更: 增加了 msg 形参。

    在 3.11 版本发生变更: `msg` 形参将从被取消的任务传播到其等待方。

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

        async def cancel_me():
            print('cancel_me(): before sleep')

            try:
                # 等待 1 小时
                await asyncio.sleep(3600)
            except asyncio.CancelledError:
                print('cancel_me(): cancel sleep')
                raise
            finally:
                print('cancel_me(): after sleep')

        async def main():
            # 创建一个 "cancel_me" 任务
            task = asyncio.create_task(cancel_me())

            # 等待 1 秒
            await asyncio.sleep(1)

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

        asyncio.run(main())

        # 预期的输出：
        #
        #     cancel_me(): before sleep
        #     cancel_me(): cancel sleep
        #     cancel_me(): after sleep
        #     main(): cancel_me is cancelled now

  cancelled()

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

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

  uncancel()

    递减对此任务的取消请求计数。

    返回剩余的取消请求数量。

    请注意一旦被取消的任务执行完成，继续调用 `uncancel()` 将是无效的。

    Added in version 3.11.

    此方法是供 asyncio 内部使用而不应被最终用户代码所使用。特别地，在一个 Task 成功地保持未取消状态的时候使用，这可以允许结构化的并发元素如 任务组 和 `asyncio.timeout()` 继续运行，将取消操作隔离在相应的结构化代码块中。例如:

        async def make_request_with_timeout():
            try:
                async with asyncio.timeout(1):
                    # 受超时影响的结构块：
                    await make_request()
                    await make_another_request()
            except TimeoutError:
                log("There was a timeout")
            # 不受超时影响的外层代码：
            await unrelated_code()

    带有 `make_request()` 和 `make_another_request()` 的代码块可能因超时而被取消，而 `unrelated_code()` 应当在超时的情况下继续运行。这是通过 `uncancel()` 实现的。 `TaskGroup` 上下文管理器也会以类似的方式来使用 `uncancel()`。

    如果最终用户代码出于某种原因通过捕获 `CancelledError` 抑制撤销操作，那么它需要调用此方法来移除撤销状态。

    当该方法将取消计数递减至零，该方法会检查之前的 `cancel()` 调用是否已安排将 `CancelledError` 抛出到任务中。如果尚未抛出，则该安排将被撤销（通过重置内部的 `_must_cancel` 旗标）。

  在 3.13 版本发生变更: 更改为在到达零值时撤回待处理的取消请求。

  cancelling()

    返回对此 Task 的挂起请求次数，即对 `cancel()` 的调用次数减去 `uncancel()` 的调用次数。

    请注意如果该数字大于零但相应 Task 仍在执行，`cancelled()` 仍将返回 `False`。这是因为该数字可通过调用 `uncancel()` 来减少，这会导致任务在取消请求降到零时尚未被取消。

    此方法是供 asyncio 内部使用而不应被最终用户代码所使用。请参阅 `uncancel()` 了解详情。

    Added in version 3.11.