Tornado4.2源码分析-Coroutine(协程)

#Coroutines

作者:MetalBug
时间:2015-03-17
出处:http://my.oschina.net/u/247728/blog
声明:版权所有,侵犯必究

tornado.gen — Simplify asynchronous code tornado.concurrent — Work with threads and futures

##1.Future Future用于保存异步任务的结果。 在同步应用中,通常用Future存储来自其他线程异步任务的结果。 但是在Tornado中,Future并不是线程安全的,这是处于效率的考虑,因为Tornado模型的使用的是 one loop per thread

###1.1内部实现-实现细节 在Future中,使用了_TracebackLogger 用于记录exception。 在设置了exception(即异步任务抛异常),添加_TracebackLogger。 当调用了Funture.result(),Future.exception()时会将_TracebackLogger清除。因为调用者已经知道了出现异常。 只有在没有调用以上函数时,Future被析构时_TracebackLogger会将错误信息记录。这样防止异步任务中出现异常却因为没被调用导致异常不被意识到。

Future.set_exec_info中,添加_TracebackLogger

def set_exc_info(self, exc_info):
    ##
    if not _GC_CYCLE_FINALIZERS:
        self._tb_logger = _TracebackLogger(exc_info)

__del__中,记录exception

 if _GC_CYCLE_FINALIZERS:
    def __del__(self):
        if not self._log_traceback:
            # set_exception() was not called, or result() or exception()
            # has consumed the exception
            return

        tb = traceback.format_exception(*self._exc_info)

        app_log.error('Future %r exception was never retrieved: %s',
                      self, ''.join(tb).rstrip())

##2.coroutine coroutine是一个修饰符,使用coroutine,可以将异步的操作以一个Generator实现,而不用写一系列回调函数。

示例:

通常情况下异步操作需要写成回调函数:

class AsyncHandler(RequestHandler):
    @asynchronous
    def get(self):
        http_client = AsyncHTTPClient()
        http_client.fetch("http://example.com",
                          callback=self.on_fetch)

    def on_fetch(self, response):
        do_something_with_response(response)
        self.render("template.html")

使用coroutine,可以写成一个Generator形式,而不用写成多个回调。

class GenAsyncHandler(RequestHandler):
    @gen.coroutine
    def get(self):
        http_client = AsyncHTTPClient()
        response = yield http_client.fetch("http://example.com")
        do_something_with_response(response)
        self.render("template.html")

在用coroutine修饰的Generator中,在遇到异步函数时,使用yield,这里需要注意的是在Tornado中,异步函数返回的大部分是Future(还可以是dictlist 可用convert_yield转换),这样yield返回的的Future.result()

coroutine内部通过_make_coroutine_wrapperRunner实现。 流程图如下: Tornado4.2-coroutine

###1._make_coroutine_wrapper

####内部实现-主要函数 _make_coroutine_wrapper可以认为是一个Inline的Runner。 因为不能保证每个被修饰的func都是Generator,都会yield,所以将获取第一个yield分离开来减少了初始化Runner的开销。

其主要做了以下工作:

  1. 调用func,得到result

  2. 如果result是Generator,调用Generator.next得到第一个yield,调用Runner处理之后工作

    def _make_coroutine_wrapper(func, replace_callback): @functools.wraps(func) def wrapper(*args, **kwargs): future = TracebackFuture() ### try: result = func(*args, **kwargs) ### else: if isinstance(result, types.GeneratorType): try: ##用于检查栈的一致性,因为yield能够在StackContext中返回 orig_stack_contexts = stack_context._state.contexts yielded = next(result) ### ##generator已经执行完毕,或者通过yield返回一个Return(Exception) except (StopIteration, Return) as e: future.set_result(getattr(e, 'value', None)) except Exception: future.set_exc_info(sys.exc_info()) ### else: ##调用Runner处理后续工作 Runner(result, future, yielded) try: return future finally: future = None future.set_result(result) return future return wrapper

####内部实现-实现细节

_make_coroutine_wrapper在返回Future时,使用了try...finally。 这里对内存进行了优化,如果在next(result)时抛出异常,那么Future.set_exc_info会被调用,这时候_TracebackLogger记录当前栈内容(使用traceback),也包含了future本身所在栈,这样出现了循环引用,将函数内即本地的future置为None,能够避免循环,从而提高GC回收效率。

# Subtle memory optimization: if next() raised an exception,
# the future's exc_info contains a traceback which
# includes this stack frame.  This creates a cycle,
# which will be collected at the next full GC but has
# been shown to greatly increase memory usage of
# benchmarks (relative to the refcount-based scheme
# used in the absence of cycles).  We can avoid the
# cycle by clearing the local variable after we return it.
try:
    return future
finally:
    future = None

###2.Runner

Runner处理Generator,将执行结果以Future返回。

其工作流程如下:

  1. yield返回的是Future,得RunnerFuture的result,调用Generator.send将result返回到Generator
  2. 继续调用Generator.next得到下一个yield,回到1

####内部实现-数据结构

self.gen 处理的Generator self.result_future 用于存储result的Future self.future 当前yield的异步函数的Future

####内部实现-主要函数

在初始化中,可以看到主要涉及到的是handle_yieldrun函数。

def __init__(self, gen, result_future, first_yielded):
    ###
    if self.handle_yield(first_yielded):
        self.run()

handle_yield 处理当前yield的异步函数。 如果异步函数已经完成(即self.future.done()),那么返回Ture。 否则利用IOLoop.add_futureself.run注册到IOLoop中,所以当self.future完成时,调用self.run

if not self.future.done() or self.future is moment:
        self.io_loop.add_future(
            self.future, lambda f: self.run())
        return False
return True

run 开始和重启Generator,持续执行到下一个yield。当Generator已经完成,返回设置好结果的Future

def run(self):
    ###
    try:
        self.running = True
        while True:
            future = self.future
            ###
            try:
            ###
                try:
                    value = future.result()
                ###
                else:
                ###将self.future.result()发送到generator中,重启generator
                    yielded = self.gen.send(value)
                ###
            ###generator结束,设置self.result_future然后返回
            except (StopIteration, Return) as e:
                self.finished = True
                self.future = _null_future
                if self.pending_callbacks and not self.had_exception:
                    raise LeakedCallbackError(
                        "finished without waiting for callbacks %r" %
                        self.pending_callbacks)
                self.result_future.set_result(getattr(e, 'value', None))
                self.result_future = None
                self._deactivate_stack_context()
                return
            ###
            if not self.handle_yield(yielded):
                return
    finally:
        self.running = False

转载于:https://my.oschina.net/u/247728/blog/387836

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值