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语句了。