tornado.gen.coroutine-协程

本文深入探讨了Tornado框架中的Coroutine装饰器如何实现回调函数的同步方式,极大提升代码可读性。通过yield、Future、ioloop等关键模块的运用,实现了异步操作的高效管理。详细解析了Coroutine装饰器的工作流程,包括yield的基本概念、Future的用法、ioloop的常用接口以及gen.coroutine的应用与源码实现,同时展示了如何在实际代码中应用Coroutine装饰器以简化回调函数的编写。
tornado的coroutine装饰器,使得回调函数可以用同步的方式实现,极大提高了代码的可读性。它的实现涉及到了yield,ioloop和Future的模块。

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的应用

直接上官网的例子

普通的回调函数的方式:

?
1
2
3
4
5
6
7
8
9
10
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" )

同步的方式:

?
1
2
3
4
5
6
7
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的源码

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

接着看_make_coroutine_wrapper的源码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
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


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

?
1
future  =  TracebackFuture()

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


?
1
result  =  func( * args,  * * kwargs)

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


?
1
yielded  =  next (result)

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


?
1
Runner(result, future, yielded)

实例化Runner,这步很重要。


?
1
return  future

返回future。


6. Runner类的实现

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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的代码。

?
1
2
3
4
5
6
7
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的函数。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
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


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


?
1
value  =  future.result()

获取yielded的结果值。


?
1
yielded  =  self .gen.send(value)

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


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

调用set_result存储结果,返回。

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


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

?
1
2
if  not  self .handle_yield(yielded):
                     return

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

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

评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值