这篇文章假设你已经看过了前两篇文章, 理解了什么是协程, 也理解了 async def
和 await
的用法.
这篇和之后的几篇文章会介绍 Python 的协程库 asyncio.
asyncio 是 Python 3.4 引入的标准库, 它提供了一些 API, 用于编写异步代码, 例如异步 I/O, 异步网络等. asyncio 也提供了一些工具, 用于编写并发代码, 例如协程, 任务, 事件循环等.
asyncio 目前大部分都使用 C 语言实现, 同时也保留了等价的 Python 实现作为 fallback. 因为两个实现在目标上是一致的 (细节上有区别), 本文会基于更容易理解的 Python 实现来讲解.
asyncio 的 Future
代码位于 asyncio/futures.py
. 文档位于
对于用户而言, Future
是同步代码和异步代码之间的桥梁.
他记录了如下变量:
state
: 任务的状态, 分别是PENDING
,CANCELLED
,FINISHED
.loop
: 事件循环, 用于执行回调函数.callbacks
: 回调函数列表, 用于存储回调函数.result
: 任务的结果.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
. 文档位于
Task
是 Future
的子类, 他的作用是把协程对象包装成 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
本质上有三类状态:
- 等着某个
Future
完成, 并且自己的唤醒回调已经注册这个Future
上. - 在事件循环里面排队等执行
__step()
. __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
).
然后新创建一个 Future
叫 outer
, 再给 outer
等待的 Future
注册完成回调函数, 通知 outer
自己完成了.
outer
在全部 Future
完成时也完成.
以上就是“Python asyncio 中的 Future 和 Task”的全部内容,希望对你有所帮助。
关于Python技术储备
学好 Python 不论是就业还是做副业赚钱都不错,但要学会 Python 还是要有一个学习规划。最后大家分享一份全套的 Python 学习资料,给那些想学习 Python 的小伙伴们一点帮助!
一、Python所有方向的学习路线
Python所有方向的技术点做的整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。
二、Python必备开发工具
三、Python视频合集
观看零基础学习视频,看视频学习是最快捷也是最有效果的方式,跟着视频中老师的思路,从基础到深入,还是很容易入门的。
四、实战案例
光学理论是没用的,要学会跟着一起敲,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。
五、Python练习题
检查学习结果。
六、面试资料
我们学习Python必然是为了找到高薪的工作,下面这些面试题是来自阿里、腾讯、字节等一线互联网大厂最新的面试资料,并且有阿里大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。
最后祝大家天天进步!!
上面这份完整版的Python全套学习资料已经上传至CSDN官方,朋友如果需要可以直接微信扫描下方CSDN官方认证二维码免费领取【保证100%免费】。