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