文档章节

python3-asyncio 学习笔记 3 -- run_until_complete

L很失败L
 L很失败L
发布于 2017/03/24 22:30
字数 1343
阅读 255
收藏 0

在笔记1中追踪了 call_soon 这中调用方式是如何运行的,这次看一看 run_until_complete 是怎么工作的.

还是由一个简单的例子开始:

import asyncio


async def hi(loop):
    print("Hello World")

loop = asyncio.get_event_loop()
# Blocking call which returns when the display_date() coroutine is done
loop.run_until_complete(hi(loop))
loop.stop()
loop.close()

一开始还是通过 asyncio.get_event_loop() 得到事件循环, 之后调用了 run_until_complete ,而调用的参数是一个 async 修饰过的函数的返回值,

async def hi():
    print("Hello World")

a = hi()
print(type(a))
#Out[3]: coroutine

可以通过简单的调用 type 来得到它的返回类型, 至于到底是什么, 可参见 asyncio.coroutine , 如果非要追究到底,其实在cpython的源码中是有写的,

typedef struct {
    /* The gi_ prefix is intended to remind of generator-iterator. */
    _PyGenObject_HEAD(gi)
} PyGenObject;
typedef struct {
    _PyGenObject_HEAD(cr)
} PyCoroObject;

可以看出 coroutine 生成器 其实是一个根源.因此通过 dir() 得到的结果也是很相似的.

回到 run_until_complete , 我们将一个 coroutine 传给它,之后发生了什么?

def run_until_complete(self, future):
    self._check_closed()
    future = tasks.ensure_future(future, loop=self)
    future.add_done_callback(_run_until_complete_cb)
    try:
        self.run_forever()
    except:
        ...
    future.remove_done_callback(_run_until_complete_cb)
    if not future.done():
        raise RuntimeError('Event loop stopped before Future completed.')
    return future.result()

去点注释和非关键部分就如上所示.可以看到传进去的 coroutine 又传给了 tasks.ensure_future, 继续追踪,发现其实这个 coroutine 传到了 tasks.Task 中用来构造 Task 对象了.而在构造 Task 对象之后或者准确的说的是构造的末尾, 通过 self._loop.call_soon(self._step) 调用了 Task 对象的 _step 方法. 在其中,通过

result = coro.send(None)

开始执行我们传递进去的 coroutine, 由于出自生成器, 所以 send 的调用作用是一致的, 都是执行到 挂起 的位置返回. 比如下面简单的示例,

async def hi():
    print("Hello World")

a = hi()
try:
    result = a.send(None)
except StopIteration as exc:
    print(exc.value)

#输出结果如下
Hello World
None

同理,这里的 result = coro.send(None) 也是如此, 如果触发了 StopIteration, 说明 coroutine 已经运行完了.如果没有呢, 比如在 coroutine 中遇到了 挂起 呢?

blocking = getattr(result, '_asyncio_future_blocking', None)

首先会检查是否阻塞.先说没有阻塞即 blocking is None 的情况:

  • 情况一 inspect.isgenerator(result) result是一个生成器:

    这种在新的语法中是不允许的, 及在 coroutine 中不允许再使用yield, 而应使用 yield from

  • 情况二 result is None:

    这种是​ coroutine 返回了一次迭代但是并没有执行完, 这种可以看到是直接通过

    self._loop.call_soon(self._step) 进行了下一次迭代.

  • 剩余情况 :

    错误情况,直接抛出异常

blocking  非 None 的情况呢? 首先要知道什么 coroutine 会返回 blocking is not None.

我们可以简单的从字面看出, coroutine 中需要有阻塞才可以. 可以简单的尝试三个情景:

import asyncio
async def no_block_no_return():
    print("haha")
async def no_block_with_return():
    print("haha")
    return 1
async def with_block():
    await asyncio.sleep(1.0)
    print("haha")
    return 1

first = no_block_no_return()
second = no_block_with_return()
third = with_block()

try:
    first_result = first.send(None)
except StopIteration as exc:
    print(exc.value)
try:
    second_result = second.send(None)
except StopIteration as exc:
    print(exc.value)
try:
    third_result = third.send(None)
except StopIteration as exc:
    print(exc.value)

'''
result:
haha
None
haha
1
'''

再检查,发现只有 third_result 是有定义的, 而且是 _asyncio.Future, 这时检查 blocking

assert getattr(third_result, '_asyncio_future_blocking', None)

会发现这就是我们要找的 blocking is not None.  也就是在 coroutine 中触发另一个coroutine.

这个 _asyncio.Future 是c实现的 Future ,可以在 CPython 源码的 Modules/_asynciomodule.c 中找到相关的定义, 其实和 asyncio.Future 是类似的.所以我们这里参考python实现的 Future.

那么 await 到底发生了什么?

其实要理解await到底做了啥也不难.前面已经说过,coroutine 和生成器类似.比如下面的代码:

async def with_block():
    await asyncio.sleep(1.0)
    print("haha")
    return 1

翻译成没有await关键字的python代码就成了:

def fake_with_block():
    #asyncio.sleep 示意如下
    loop = asyncio.get_event_loop()
    future = loop.create_future()
    h = future._loop.call_later(
            1.0, futures._set_result_unless_cancelled, future,None)
    def _await():
        while not future.done():
            yield future
    yield from _await()
    #以上就是await的不完全等价的一个示意,注:只适用于此特殊情景,不能套用
    print("haha")
    return 1

也就是await后面的(同一行)语句(表达式)悄悄的执行了并和生成器似的返回了,不过返回了一个future对象.

所以在blocking is not None 的语块内可以看到:

result.add_done_callback(self._wakeup)

也就是添加了future结束后的操作.那怎么知道 await后面的(同一行)语句(表达式)运行完了呢?于是asyncio.future中有了set_result的方法.通过set_result来表明future执行完了并执行绑定的call_back.而这里的sleep是通过call_later延迟调用的方式执行set_result.

于是所有的操作就都合并到了loop._run_once中了.这样就完整的循环起来了.

await后面(后面的行)还有语句要执行啊?添加的那个call_back,就是负责await部分完成了后面怎么运行的.

def _wakeup(self, future):
    try:
        future.result()
    except Exception as exc:
        self._step(exc)
    else:
        self._step()
    self = None

可以看到,await执行完,如果没有异常,还是继续_step也就是coro.send(None),也就是继续await后面(后面的行)的内容,这样一个coro就能完整的运行了.

至此,future = tasks.ensure_future(future, loop=self) 就剖析完了.后面的

future.add_done_callback(_run_until_complete_cb)
try:
    self.run_forever()
except:
        ...
future.remove_done_callback(_run_until_complete_cb)

就是异常处理和开始事件循环,在笔记1中已经提过了就不再赘余.

© 著作权归作者所有

L很失败L
粉丝 2
博文 14
码字总数 7388
作品 0
合肥
程序员
私信 提问
python学习笔记 | Python中并行IO操作的内存效率

Python允许多种不同的并行处理方法。并行性的主要问题是了解其局限性。我们要么平行IO操作或像图像处理这样的CPU限制任务。 在Python 3.5之前,有两种方法可以并行处理IO绑定操作。本地方法是...

跨界的聚能
2018/05/24
0
0
python异步编程之asyncio(百万并发)

一、asyncio下面通过举例来对比同步代码和异步代码编写方面的差异,其次看下两者性能上的差距,我们使用sleep(1)模拟耗时1秒的io操作。 同步代码: import time def hello(): 输出:(间隔差...

p柯西
2018/06/12
0
0
Python中异步协程的使用方法介绍

1. 前言 在执行一些 IO 密集型任务的时候,程序常常会因为等待 IO 而阻塞。比如在网络爬虫中,如果我们使用 requests 库来进行请求的话,如果网站响应速度过慢,程序一直在等待网站响应,最后...

崔庆才
2018/07/06
0
0
Python 3.4 中新的 asyncio : Servers、Protocols 和 Transports

在之前的一篇文章中,我介绍了Python 3.4 中新引入的 asyncio 模块,我解释了事件循环函数的注册,执行以及延迟或取消调用的简单使用方法。我将在这里演示更高级的例子,探索asyncio对服务器...

oschina
2014/06/27
9.2K
7
如何利用并发性加速你的python程序(二):I/O 绑定程序加速

雷锋网 AI 科技评论按,本文是工程师 Jim Anderson 分享的关于「通过并发性加快 python 程序的速度」的文章的第二部分,主要内容是 I/O 绑定程序加速相关。 在上一篇中,我们已经讲过了相关的...

王雪佩
01/31
0
0

没有更多内容

加载失败,请刷新页面

加载更多

C 语言 二级指针的使用

#include <stdio.h>#include <stdlib.h>typedef struct node Node;struct node {int data;struct node* next;struct node* prev;};Node head;Node* insert(Node......

小张525
3分钟前
0
0
【大数据技术】——Hadoop(1)

什么是大数据 基本概念 《数据处理》 在互联网技术发展到现今阶段,大量日常、工作等事务产生的数据都已经信息化,人类产生的数据量相比以前有了爆炸式的增长,以前的传统的数据处理技术已经...

须臾之余
17分钟前
5
0
比特币从地址逆向计算私钥

区块链 区块链简介 说到比特币,就不得不提区块链。那什么是区块链呢? 区块链本质是一个数据集,只不过数据的组织采用了比较特殊的方式,就是把数据拆分为一块一块的小数据集。 为什么要进行...

trayvon
31分钟前
0
0
TypeScript……真香

写前端或者用 node 写命令行小工具一直采用的 es6 的语法,对于 TypeScript 则是秉持敬而远之的态度,毕竟团队中多推广一门语言所需要花费的精力都是让人望而却步的。所以对于 JavaScript 的...

郁也风
37分钟前
3
0
shell基本案例

1、自定义rm linux系统的rm命令太危险,一不小心就会删除掉系统文件。 写一个shell脚本来替换系统的rm命令,要求当删除一个文件或者目录时,都要做一个备份,然后再删除。下面分两种情况,做...

寰宇01
43分钟前
3
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部