Tornado 里的协程

在使用Tornado的时候经常会写这样的代码:

@gen.coroutine
def func():
    key = object()
    callback = yield Callback(key)
    do_work()
    ret = yield Wait(key)
    reutrn ret

每次看都觉得像魔法, 于是今天看了一下tornado的代码来瞅瞅发生了什么.

首先,我们找到这段代码在哪里被@gen.coroutine装饰

def _make_coroutine_wrapper(func, replace_callback):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        runner = None
        future = TracebackFuture() #创建了一个新的Future对象,这货就是Future.

        if replace_callback and 'callback' in kwargs:
            callback = kwargs.pop('callback')
            IOLoop.current().add_future(
                future, lambda future: callback(future.result())) #当future执行完就把callback加入ioloop.

        try:
            result = func(*args, **kwargs) #调用被装饰函数
        except (Return, StopIteration) as e:
            result = getattr(e, 'value', None)
        except Exception:
            future.set_exc_info(sys.exc_info())
            return future
        else:
            if isinstance(result, types.GeneratorType): #如果被装饰函数被调用后产生一个Generator就用一个Runner来让future调用result.
                runner = Runner(result, future)
                runner.run()
                return future
        future.set_result(result)
        return future
    return wrapper

下面进入Runner, 直接看run方法, 对于Py来说, 这函数真是长的出奇, 缩进也用了很多…:

    def run(self):
        """Starts or resumes the generator, running until it reaches a
        yield point that is not ready.
        """
        if self.running or self.finished:
            return
        try:
            self.running = True
            while True:
                future = self.future
                if not future.done():
                    return
                self.future = None
                try:
                    orig_stack_contexts = stack_context._state.contexts
                    try:
                        value = future.result()#如果是第一次进来, 那么返回None
                    except Exception:
                        self.had_exception = True
                        yielded = self.gen.throw(*sys.exc_info())
                    else:
                        yielded = self.gen.send(value)#如果result是None的话,就启动了这个协程,返回了Callback(key)
                    if stack_context._state.contexts is not orig_stack_contexts:
                        self.gen.throw(
                            stack_context.StackContextInconsistentError(
                                'stack_context inconsistency (probably caused '
                                'by yield within a "with StackContext" block)'))
                except (StopIteration, Return) as e:
                    self.finished = True
                    self.future = _null_future
                    if self.pending_callbacks and not self.had_exception:
                        # If we ran cleanly without waiting on all callbacks
                        # raise an error (really more of a warning).  If we
                        # had an exception then some callbacks may have been
                        # orphaned, so skip the check in that case.
                        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
                except Exception:
                    self.finished = True
                    self.future = _null_future
                    self.result_future.set_exc_info(sys.exc_info())
                    self.result_future = None
                    self._deactivate_stack_context()
                    return
                if isinstance(yielded, (list, dict)):
                    yielded = Multi(yielded)
                if isinstance(yielded, YieldPoint):#到了这里,Callback()是一个yield point
                    self.future = TracebackFuture()
                    def start_yield_point():
                        try:
                            yielded.start(self)
                            if yielded.is_ready():
                                self.future.set_result(
                                    yielded.get_result()) #把Callback()的值给Future, 这样下次再调用run的时候, Future就能把自己的result给我们定义的函数里的callback了,于是我们通过一个yield point得到了要的callback.
                            else:
                                self.yield_point = yielded 
                        except Exception:
                            self.future = TracebackFuture()
                            self.future.set_exc_info(sys.exc_info())
                    if self.stack_context_deactivate is None:
                        # Start a stack context if this is the first
                        # YieldPoint we've seen.
                        with stack_context.ExceptionStackContext(
                                self.handle_exception) as deactivate:
                            self.stack_context_deactivate = deactivate
                            def cb():
                                start_yield_point()
                                self.run()
                            self.io_loop.add_callback(cb)
                            return
                    else:
                        start_yield_point()
                elif is_future(yielded):
                    self.future = yielded
                    if not self.future.done():
                        self.io_loop.add_future(
                            self.future, lambda f: self.run())
                        return
                else:
                    self.future = TracebackFuture()
                    self.future.set_exception(BadYieldError(
                        "yielded unknown object %r" % (yielded,)))
        finally:
            self.running = False

暂时看懂了这些.关于发送一个None启动Generator的, 参见这里:看后面那个回答, 前面那个回答太基础了, 没提到send方法

    原文作者:kamushin233
    原文地址: https://segmentfault.com/a/1190000000426460
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞