Python asyncio 中的 Future 和 Task

这篇文章假设你已经看过了前两篇文章, 理解了什么是协程, 也理解了 async defawait 的用法.

这篇和之后的几篇文章会介绍 Python 的协程库 asyncio.

asyncio 是 Python 3.4 引入的标准库, 它提供了一些 API, 用于编写异步代码, 例如异步 I/O, 异步网络等. asyncio 也提供了一些工具, 用于编写并发代码, 例如协程, 任务, 事件循环等.

asyncio 目前大部分都使用 C 语言实现, 同时也保留了等价的 Python 实现作为 fallback. 因为两个实现在目标上是一致的 (细节上有区别), 本文会基于更容易理解的 Python 实现来讲解.

asyncio 的 Future

代码位于 asyncio/futures.py . 文档位于

对于用户而言, Future 是同步代码和异步代码之间的桥梁.

他记录了如下变量:

  1. state: 任务的状态, 分别是 PENDING , CANCELLED , FINISHED .
  2. loop: 事件循环, 用于执行回调函数.
  3. callbacks: 回调函数列表, 用于存储回调函数.
  4. result: 任务的结果.
  5. exception: 任务的异常.

一个 Future 完成了可以执行回调函数 (比如唤醒另一个 Future ). 那些等待 IO 的任务就可以把自己注册在 IO 所在的任务上, 等 IO 完成后, IO 任务会唤醒等待它的任务. 这样就实现了异步 IO.

可以想象, Future 记录了任务的状态, 并储存了任务的结果或异常. 用户可以等它, 并获取结果, 也可以取消他.

值得一提的是 Future__await__ 方法.

def __await__(self):
    if not self.done():
        self._asyncio_future_blocking = True
        yield self  # This tells Task to wait for completion.
    if not self.done():
        raise RuntimeError("await wasn't used with future")
    return self.result()  # May raise too.

当你 await 一个 Future` 的时候, 会调用 __await__ 方法. 这个方法会返回一个生成器, 这个生成器会 yield 自己. 这样, 调用者就知道应该等待这个 Future 了.

Future 还会设置一个 _asyncio_future_blocking 标志, 用于让外界知道这是一个 Future . 这个标志马上会在 Task 里面看到.

asyncio 的 Task

Task 的 __step 方法

代码位于 asyncio/tasks.py . 文档位于

TaskFuture 的子类, 他的作用是把协程对象包装成 Future .

Task 里面推动执行的函数是 __step . 在 Task 创建的时候, 他要么会把 __step 注册到事件循环中, 要么就立即启动.

def __step(self, exc=None):
    if self.done():
        raise exceptions.InvalidStateError(
            f'_step(): already done: {self!r}, {exc!r}')
    if self._must_cancel:
        if not isinstance(exc, exceptions.CancelledError):
            exc = self._make_cancelled_error()
        self._must_cancel = False
    self._fut_waiter = None

    _enter_task(self._loop, self)
    try:
        self.__step_run_and_handle_result(exc)
    finally:
        _leave_task(self._loop, self)
        self = None  # Needed to break cycles when an exception occurs.

__step 里面做了一些准备工作, 核心逻辑还是在 __step_run_and_handle_result 里面.

Task 的 __step_run_and_handle_result 方法

这个函数比较长, 我们分为几部分来看.

def __step_run_and_handle_result(self, exc):
    coro = self._coro
    try:
        if exc is None:
            # We use the `send` method directly, because coroutines
            # don't have `__iter__` and `__next__` methods.
            result = coro.send(None)
        else:
            result = coro.throw(exc)

第一步, 仍然是执行这个协程, 就像我们上篇文章中所指出的 send 函数. 这里只会向其中 send(None) .


except StopIteration as exc:
    if self._must_cancel:
        # Task is cancelled right before coro stops.
        self._must_cancel = False
        super().cancel(msg=self._cancel_message)
    else:
        super().set_result(exc.value)

第二步, 如果协程抛出了 StopIteration 异常, 说明协程执行完毕. 这时候我们会把结果设置到 Future 上. super() 用来调用父类方法.


except exceptions.CancelledError as exc:
    # Save the original exception so we can chain it later.
    self._cancelled_exc = exc
    super().cancel()  # I.e., Future.cancel(self).
except (KeyboardInterrupt, SystemExit) as exc:
    super().set_exception(exc)
    raise
except BaseException as exc:
    super().set_exception(exc)

第三步, 处理一些常见的异常状况. 比如被取消了, 按了退出键等等.


第四步, 如果上面还没有处理完, 那么说明协程里面 yield 出来了某个东西.

如果 yield 出来的是一个 asyncio.Future , 那么我们还要做一些特殊处理: 把唤醒自己的任务, 加入它的完成回调.

另一种情况是 yield 出来的是 None , 这种情况我们只需要把自己加入事件循环就行了.

(下面改写了代码, 把错误分支都 assert False 方便读者理解)

else:
    blocking = getattr(result, '_asyncio_future_blocking', None)
    if blocking is not None:
        assert blocking
        assert result is not self
        # result 是一个 asyncio.Future
        # 我们在等待它完成

        # 这里在把自己加入 asyncio.Future 完成时的回调
        result._asyncio_future_blocking = False
        result.add_done_callback(
            self.__wakeup, context=self._context)
        self._fut_waiter = result
        if self._must_cancel:
            if self._fut_waiter.cancel(
                    msg=self._cancel_message):
                self._must_cancel = False

    elif result is None:
        # 普通的 yield None 则只需要
        # 把自己加入事件循环
        self._loop.call_soon(self.__step, context=self._context)
    else:
        assert False

小结

总结一下, Task__step 方法会执行协程, 并处理协程的结果.

如果协程 yield 出来的是一个 asyncio.Future , 那么 Task 会把自己加入这个 Future 的完成回调. 如果协程 yield 出来的是 None , 那么 Task 会把自己加入事件循环.

可以看出, Task 本质上有三类状态:

  1. 等着某个 Future 完成, 并且自己的唤醒回调已经注册这个 Future 上.
  2. 在事件循环里面排队等执行 __step().
  3. __step() 正在执行.

asyncio 的 gather 原理

asyncio.gather 是一个很常用的函数, 用于并发执行多个协程.

用法如下:

async def coro():
    pass

async def main():
    await asyncio.gather(
        coro(),
        coro(),
        coro(),
    )

三个 coro() 都会被注册到时间循环中, 并且 main() 会等待他们全部执行完毕.

相信聪明的你已经想到了实现, 首先把每个协程/生成器/ Future 包装成 Future 对象 (比如通过 asyncio.ensure_future ).

然后新创建一个 Futureouter , 再给 outer 等待的 Future 注册完成回调函数, 通知 outer 自己完成了.

outer 在全部 Future 完成时也完成.

以上就是“Python asyncio 中的 Future 和 Task”的全部内容,希望对你有所帮助。

关于Python技术储备

学好 Python 不论是就业还是做副业赚钱都不错,但要学会 Python 还是要有一个学习规划。最后大家分享一份全套的 Python 学习资料,给那些想学习 Python 的小伙伴们一点帮助!

一、Python所有方向的学习路线

Python所有方向的技术点做的整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。

在这里插入图片描述

二、Python必备开发工具

img

三、Python视频合集

观看零基础学习视频,看视频学习是最快捷也是最有效果的方式,跟着视频中老师的思路,从基础到深入,还是很容易入门的。

img

四、实战案例

光学理论是没用的,要学会跟着一起敲,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。

img

五、Python练习题

检查学习结果。

img

六、面试资料

我们学习Python必然是为了找到高薪的工作,下面这些面试题是来自阿里、腾讯、字节等一线互联网大厂最新的面试资料,并且有阿里大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。

img

最后祝大家天天进步!!

上面这份完整版的Python全套学习资料已经上传至CSDN官方,朋友如果需要可以直接微信扫描下方CSDN官方认证二维码免费领取【保证100%免费】。

  • 26
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值