环境配置
win10
pycharm64 5.0.3
python 3.6.7
tornado 5.1.1
示例代码
from tornado.gen import coroutine
@coroutine
def asyn_sum(a, b):
print("begin calculate:sum %d+%d"%(a,b))
result = yield simple_plus(a, b)
print("after yielded")
print("the %d+%d=%d" % (a, b, result))
@coroutine
def simple_plus(a, b):
return a+b
def main():
asyn_sum(2, 3)
# tornado.ioloop.IOLoop.instance().start()
if __name__ == "__main__":
main()
执行过程
被@coroutine
装饰的方法实际执行的函数是gen.py中的_make_coroutine_wrapper
装饰器返回的方法wrapper
(暂且称之为asyn_sum_wrapper
),且wrapper
中的func
是types.coroutine
处理后的方法。
asyn_sum执行过程
asyn_sum_wrapper
方法的执行结果result = func(*args, **kwargs)
实际是一个GeneratorType
类型的对象,实际的执行是在yielded = next(result)
中。执行到result = yield simple_plus(a, b)
时,进入simple_plus
被装饰后的方法wrapper
中(暂且称之为simple_plus_wrapper
)。simple_plus_wrapper
返回一个Future对象,其result
属性为5,即其实际返回值,随后跳转到runner = Runner(result, future, yielded)
。
Runner
的实例化有一段执行代码
if self.handle_yield(first_yielded):
gen = result_future = first_yielded = None
self.run()
self.handle_yield
会将self.future
置为first_yielded
,然后各个指向这个future的变量置空;在self.run()
中self.future
会赋值给局部变量future
,然后被置空;在执行value = future.result()
后该局部变量也被置空。future最终只有外层的Runner
初始化时候的引用,Runner
返回后yielded=None
,等待释放。
获得了simple_plus
返回结果后,gen.py使用yielded = self.gen.send(value)
回归到asyn_sum
的yield,并将返回值写入result
,此时self.run()
被挂起,等asyn_sum
执行完后恢复执行。
asyn_sum
执行完后,会跳转到异常捕获逻辑except (StopIteration, Return) as e:
,e
就是asyn_sum
的返回值,后续被future_set_result_unless_cancelled(self.result_future, _value_from_stopiteration(e))
将值写回到result_future
中,即asyn_sum_wrapper
调用初期实例化的future对象。
simple_plus执行过程
simple_plus
同样也是在simple_plus_wrapper
的result = func(*args, **kwargs)
处执行,不同的是,simple_plus
方法体不包含yield
,所以在types.coroutine.wrapped
返回的不再是GeneratorType
类型的对象,而是simple_plus
方法的实际执行结果5,然后在装饰器内会包装上Future返回给上层调用者。
future
@coroutine
装饰器的wrapper
中,第一步就是定义一个future
对象,在asyn_sum_wrapper
执行过程中,由于内部调用了yield
,所以在它的future
被使用前,又进入了simple_plus_wrapper
。由于后者的result是非GeneratorType
类型,所以跳转到了future_set_result_unless_cancelled(future, result)
。
从gen.py中可以追溯Future的定义位置,根据实际单步定位,Future定义在_asyncio.py中,因为future
的方法无法跳转。
# gen.py
from tornado.concurrent import (Future, is_future, chain_future, future_set_exc_info,
future_add_done_callback, future_set_result_unless_cancelled)
# concurrent.py
if asyncio is not None:
Future = asyncio.Future # noqa
if futures is None:
FUTURES = Future # type: typing.Union[type, typing.Tuple[type, ...]]
else:
FUTURES = (futures.Future, Future)
协程切换
参考自关于协程的 send(None)
为方便对yielded = self.gen.send(value)
的理解,此处保留代码示例。
def consumer():
r = ''
while True:
n = yield r
if not n:
return
print('[CONSUMER] Consuming %s...' % n)
r = '200 OK'
def produce(c):
c.send(None)
n = 0
while n < 5:
n = n + 1
print('[PRODUCER] Producing %s...' % n)
r = c.send(n)
print('[PRODUCER] Consumer return: %s' % r)
c.close()
c = consumer()
produce(c)
未了解部分
Runner中的self._deactivate_stack_context()
、future_set_exc_info(self.result_future, sys.exc_info())
小结
tornado中每一个被coroutine装饰的方法都会对应一个future,根据调用的深度,future的嵌套深度逐渐增加,直到某一个future set_result(result = func(*args, **kwargs)
返回结果为非生成器类型),然后进行后续future的嵌套处理。
对yield AsyncHTTPClient的调用,yielded = next(result)
会获取到fetch返回的future对象,与调用者及其future对象一并生成一个Runner对象,供IOloop异步回调,runner中yielded = self.gen.send(value)
会触发协程从断点继续执行。
网友评论