再次阅读 Tornado 中的 coroutine 代码
admin
2023-07-31 01:44:35
0

一年多前的文章 http://segmentfault.com/a/1190000000426460 这次重新读,再加上一些 js 中 co给我的知识,有了更深的理解。

Front Knowledge

yield and generator will be the front knowledge of this article. And you should also have some sense of epoll/kqueue and callback style. Let\’s enjoy the source code of the implement of coroutine.

Python Tornado

A simple async fetch function used by a coroutine in Python, exception handle removed

def fetch(self, request):
    future = TracebackFuture() # TracebackFuture == Future
    def handle_response(response):
        future.set_result(response)
    self.fetch_impl(request, handle_response) # This is a async function
    return future

def fetch_impl(self, request, callback):
    pass

future — an instance of Future — is an object that used to collect and send result to generator.

A coroutine that uses above fetch

@gen.coroutine
def request(self, uri):
    response = yield http.fetch(uri)

And we all know @gen.coroutine is a syntax sugar of

request = gen.coroutine(request)

coroutine wrapper function, also exception handle removed

def _make_coroutine_wrapper(func, replace_callback):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        future = TracebackFuture()
        try:
            result = func(*args, **kwargs) # result is a generator if the function is a generator function
        except (Return, StopIteration) as e:
            result = getattr(e, \'value\', None)
        else:
            if isinstance(result, types.GeneratorType): # if the function is a generator function
                try:
                    yielded = next(result) # generator.next() will run the code above and right hand of the generator, for our example request here, http.fetch(uri) will run and return yielded(a instance of Future).
                except (StopIteration, Return) as e:
                    future.set_result(getattr(e, \'value\', None))
                else:
                    Runner(gen=result, result_future=future, first_yielded=yielded) # Runner is like the co lib in Js written by TJ, Runner use a While True rather than recursive, because recursive is slower in Python.
                return future
            else: # or the function is jsut a normal function
              pass 
        future.set_result(result)
        return future
    return wrapper

With the Tornado usage we can learn that the function after yield can be either a coroutine or a normal function.
Both of them returns a Future. You can write return Future by yourself or use @coroutine. But make sure your normal function is an async function.

Runner.run function, exception handle removed

def __init__(self, gen, result_future, first_yielded): # init of Runner
    ... # some attrs bind
    self.future = first_yielded # removed some complex logic, just show the basic logic of running the `request` generator.
    self.io_loop.add_future(
                self.future, lambda f: self.run()) # io_loop is a epoll based loop, the second function is a callback function when future is finished.

def run(self):
\"\"\"Starts or resumes the generator, running until it reaches a
yield point that is not ready.
\"\"\"
    while True:
        if not future.done():
            return
        try:
            value = future.result()
            yielded = self.gen.send(value)
        except (StopIteration, Return) as e:
            self.finished = True
            return
        except Exception:
            self.finished = True
            return
        if not self.handle_yield(yielded):
            return

Runner is like the co lib in Js written by TJ, Runner use a While True rather than recursive, because recursive is slower in Python. Both of them do the same thing, that is executing the generator unitl it\’s done.

First of all, Runner add the future, or we can say the async function fetch to io_loop. If fetch is finish, itself will invoke the callback function handle_response to set data to future. And the io_loop will invoke another callback function lambda f: self.run() to run the function run to get the result from future by value = future.result() and send to the generator by yield = gen.send(value) and start the next block of the generator function if exists until the whole function is stoped and return a StopIteration.

So let us figure out the effect of each object:

  • generator function: a function with yield statement

  • generator: invoke a generator function will return a generator

  • coroutine: a wrapper function to wrapper a generator function. It will create a runner to run the generator.

  • Future: used to collect and get result, it\’s a result container.

  • Runner: it will register the future to io_loop and send result back to generator, and repeats unitl generator is done.

相关内容

热门资讯

Mobi、epub格式电子书如... 在wps里全局设置里有一个文件关联,打开,勾选电子书文件选项就可以了。
定时清理删除C:\Progra... C:\Program Files (x86)下面很多scoped_dir开头的文件夹 写个批处理 定...
scoped_dir32_70... 一台虚拟机C盘总是莫名奇妙的空间用完,导致很多软件没法再运行。经过仔细检查发现是C:\Program...
500 行 Python 代码... 语法分析器描述了一个句子的语法结构,用来帮助其他的应用进行推理。自然语言引入了很多意外的歧义,以我们...
小程序支付时提示:appid和... [Q]小程序支付时提示:appid和mch_id不匹配 [A]小程序和微信支付没有进行关联,访问“小...
pycparser 是一个用... `pycparser` 是一个用 Python 编写的 C 语言解析器。它可以用来解析 C 代码并构...
微信小程序使用slider实现... 众所周知哈,微信小程序里面的音频播放是没有进度条的,但最近有个项目呢,客户要求音频要有进度条控制,所...
65536是2的几次方 计算2... 65536是2的16次方:65536=2⁶ 65536是256的2次方:65536=256 6553...
Apache Doris 2.... 亲爱的社区小伙伴们,我们很高兴地向大家宣布,Apache Doris 2.0.0 版本已于...
项目管理和工程管理的区别 项目管理 项目管理,顾名思义就是专注于开发和完成项目的管理,以实现目标并满足成功标准和项目要求。 工...