tornado.gen.coroutine-协程

1.  yield的基本概念

2.  Future的用法

3.  ioloop的常用接口

4. gen.coroutine的应用

5. gen.coroutine的源码

6. Runner类的实现


1. yield的基本概念

python中使用yield实现了生成器函数,同样yield.send( )方法也实现了控制权在函数间的跳转。

关于yield比较详细的介绍,可以参考这篇博文http://www.cnblogs.com/coderzh/articles/1202040.html


2.Future的用法

Future是用来在异步过程中,存放结果的容器。它可以在结果出来时,进行回调。

add_done_callback(self, fn): 添加回调函数fn,函数fn必须以future作为参数。

set_result(self, result): 存放结果,此函数会执行回调函数。

set_exception(self, exception):存放异常,此函数会执行回调函数。


3. ioloop的常用接口

add_future(self, future, callback): 当future返回结果时,会将callback登记到ioloop中,由ioloop在下次轮询时调用。

add_callback(self, callback, *args, **kwargs): 将callback登记到ioloop中,由ioloop在下次轮询时调用。

add_timeout(self, deadline, callback, *args, **kwargs): 将callback登记到ioloop中,由ioloop在指定的deadline时间调用。

def call_at(self, when, callback, *args, **kwargs): 将callback登记到ioloop中,由ioloop在指定的when时间调用。

def call_later(self, delay, callback, *args, **kwargs): 将callback登记到ioloop中, 由ioloop在指定的延迟delay秒后调用。

add_handler(self, fd, handler, events): 将文件符fd的event事件和回调函数handler登记到ioloop中,当事件发生时,触发。


4. gen.coroutine的应用

直接上官网的例子

普通的回调函数的方式:

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")

同步的方式:

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")

可以看出代码明显清晰,简单多了。如果有深层次的回调,效果会更明显。


5. gen.coroutine的源码

def coroutine(func, replace_callback=True):
    return _make_coroutine_wrapper(func, replace_callback=True)

接着看_make_coroutine_wrapper的源码:

def _make_coroutine_wrapper(func, replace_callback):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        future = TracebackFuture()

        if replace_callback and 'callback' in kwargs:
            callback = kwargs.pop('callback')
            IOLoop.current().add_future(
                future, lambda future: callback(future.result()))

        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):
                # Inline the first iteration of Runner.run.  This lets us
                # avoid the cost of creating a Runner when the coroutine
                # never actually yields, which in turn allows us to
                # use "optional" coroutines in critical path code without
                # performance penalty for the synchronous case.
                try:
                    orig_stack_contexts = stack_context._state.contexts
                    yielded = next(result)
                    if stack_context._state.contexts is not orig_stack_contexts:
                        yielded = TracebackFuture()
                        yielded.set_exception(
                            stack_context.StackContextInconsistentError(
                                'stack_context inconsistency (probably caused '
                                'by yield within a "with StackContext" block)'))
                except (StopIteration, Return) as e:
                    future.set_result(getattr(e, 'value', None))
                except Exception:
                    future.set_exc_info(sys.exc_info())
                else:
                    Runner(result, future, yielded)
                try:
                    return future
                finally:                   
                    future = None
        future.set_result(result)
        return future
    return wrapper


这段代码注意到,有几个关键地方。

future = TracebackFuture()

TracebackFuture就是Future,两者是同一个类。这里定义了future变量, 是用来后面增加callback用的。当函数执行完时,会将这个future返回。 


result = func(*args, **kwargs)

这里执行被装饰的函数,返回result。我们知道有yield语句的函数,返回结果是一个生成器。


yielded = next(result)

返回yield结果, next(result)被要求返回Future,list, dict,或者YieldPoint类型,一般返回Future。


Runner(result, future, yielded)

实例化Runner,这步很重要。


return future

返回future。


6. Runner类的实现

class Runner(object):
    def __init__(self, gen, result_future, first_yielded):
        self.gen = gen
        self.result_future = result_future
        self.future = _null_future
        self.yield_point = None
        self.pending_callbacks = None
        self.results = None
        self.running = False
        self.finished = False
        self.had_exception = False
        self.io_loop = IOLoop.current()
        self.stack_context_deactivate = None
        if self.handle_yield(first_yielded):
            self.run()

__init__构造函数首先初始化参数,然后调用handle_yield方法。


因为first_yielded为Future类型, 所以简化下handle_yield的代码。

def handle_yield(self, yielded):
        self.future = yielded
        if not self.future.done():
            self.io_loop.add_future(
                self.future, lambda f: self.run())
            return False
        return True

handle_yield方法,首先判断yielded是否已经返回结果,如果没有就调用io_loop.add_future方法。这样当yielded返回结果时,就会回调self.run方法。如果已经返回,就直接执行self.run方法。


self.run方法会将控制权返回给被gen.coroutine的函数。

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()
                    except Exception:
                        self.had_exception = True
                        yielded = self.gen.throw(*sys.exc_info())
                    else:
                        yielded = self.gen.send(value)
                    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 not self.handle_yield(yielded):
                    return
        finally:
            self.running = False


这里注意到几个关键地方。


value = future.result()

获取yielded的结果值。


yielded = self.gen.send(value)

使用send方法将结果返回,并且将执行权交给被装饰的函数。并且返回下一个yield的值。


如果函数生成器后面没有yield语句了,就会抛出StopIteration异常。

调用set_result存储结果,返回。

self.result_future.set_result(getattr(e, 'value', None))


如果函数生成器后面还有yield语句,就会调用handle_yield,仍然登记self.run方法。

if not self.handle_yield(yielded):
                    return

之所以要有个判断,是需要在while循环里,如果future已经有返回结果了,就继续取结果,send结果,

一直到future的结果没有返回或者没有yield语句了。

转载于:https://my.oschina.net/u/569730/blog/382661

你可能感兴趣的:(tornado.gen.coroutine-协程)