在使用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方法