zoukankan      html  css  js  c++  java
  • tornado的gen.engine浅析

    1. 首先看看示例代码,也是官方给出的:

     1 class Main(RequestHandler):
     2     @tornado.web.asynchronous
     3     @gen.engine
     4     def get(self):
     5         if _DEBUG:
     6             pdb.set_trace()
     7         http_client = AsyncHTTPClient()
     8         response = yield gen.Task(http_client.fetch, "http://code.rootk.com")
     9         self.write(str(response))
    10         self.finish()

    2. 初始化异步的HTTP客户端,tornado自带的。

    http_client = AsyncHTTPClient()

    3. @gen.engine

    这是一个装饰器,它装饰生成器函数,看看实现的代码:

     1 def engine(func):
     2     @functools.wraps(func)
     3     def wrapper(*args, **kwargs):
     4         runner = None
     5 
     6         def handle_exception(typ, value, tb):
     7             # if the function throws an exception before its first "yield"
     8             # (or is not a generator at all), the Runner won't exist yet.
     9             # However, in that case we haven't reached anything asynchronous
    10             # yet, so we can just let the exception propagate.
    11             if runner is not None:
    12                 return runner.handle_exception(typ, value, tb)
    13             return False
    14         with ExceptionStackContext(handle_exception) as deactivate:
    15             # gen是一个RequestHandler的子类的方法
    16             gen = func(*args, **kwargs)
    17             # 如果方法是一个生成器
    18             if isinstance(gen, types.GeneratorType):
    19                 # 实例化Runner类,传入生成器作为参数
    20                 runner = Runner(gen, deactivate)
    21                 runner.run()
    22                 return
    23             assert gen is None, gen
    24             deactivate()
    25             # no yield, so we're done
    26     return wrapper

    (1). 在代码runner = Runner(gen, deactivate)之中,gen指的是示例代码中get这个生成器函数。

    (2). 然后调用实例的run方法。

    4. 分析一下类的Runner实例的run方法。

    run函数循环会执行三遍:
    第一遍,当gen.engine执行时,遇到runner.run()会执行一遍,最后会调用Task的start方法
    这次执行的结果是让示例代码中的yield语句运行完毕。

    第二遍,start方法执行完毕后,http_client.fetch方法会回调Runner的result_callback方法,会再次执行。
    这次执行的结果是向生成器函数发送执行结果:
    yielded = self.gen.send(next)
    next的内容就是在上面的代码中:
    next = self.yield_point.get_result()
    而且self.finished = True,被设置为True。

    第三遍,最后start方法执行完毕(它是由第一步调用的),run方法里的循环会最后一次运行。
    当它发现self.finished被设置为True时,直接return。

    实现代码:

     1 if self.running or self.finished:
     2             return
     3         try:
     4             self.running = True
     5             while True:
     6                 # 如果上一次执行没有抛出异常
     7                 if self.exc_info is None:
     8                     try:
     9                         if not self.yield_point.is_ready():
    10                             return
    11                         # self.yield_point是一个_NullYieldPoint类的实例
    12                         # 实例的get_result方法总是返回None
    13                         next = self.yield_point.get_result()
    14                     except Exception:
    15                         self.exc_info = sys.exc_info()
    16                 try:
    17                     if self.exc_info is not None:
    18                         self.had_exception = True
    19                         exc_info = self.exc_info
    20                         self.exc_info = None
    21                         yielded = self.gen.throw(*exc_info)
    22                     else:
    23                         # 这里调用生成器函数的send方法,参数为None
    24                         # 对应到示例中的代码,就完整的执行了下面这一句
    25                         # yield gen.Task(http_client.fetch, "http://code.rootk.com")
    26                         # 返回了一个Task类的实例
    27                         # 因为可以同时执行多个任务,所以返回的也有可能是含有一个Task类的列表
    28                         # 下面会做判断
    29                         yielded = self.gen.send(next)
    30                 except StopIteration:
    31                     self.finished = True
    32                     if self.pending_callbacks and not self.had_exception:
    33                         # If we ran cleanly without waiting on all callbacks
    34                         # raise an error (really more of a warning).  If we
    35                         # had an exception then some callbacks may have been
    36                         # orphaned, so skip the check in that case.
    37                         raise LeakedCallbackError(
    38                             "finished without waiting for callbacks %r" %
    39                             self.pending_callbacks)
    40                     self.deactivate_stack_context()
    41                     return
    42                 except Exception:
    43                     self.finished = True
    44                     raise
    45                 # 判断yield返回的内容
    46                 # 如果是列表类型(说明返回了多个任务,即多个Task类的实例)
    47                 if isinstance(yielded, list):
    48                     yielded = Multi(yielded)
    49                 # 如果是YieldPoint类的实例,就是Task类本身(Task类是YieldPoint类的子类)
    50                 # 说明只有一个任务(一个Task类的实例)
    51                 if isinstance(yielded, YieldPoint):
    52                     self.yield_point = yielded
    53                     try:
    54                         # 调用Task类实例的start方法
    55                         # 参数为Runner类实例自身
    56                         self.yield_point.start(self)
    57                     except Exception:
    58                         self.exc_info = sys.exc_info()
    59                 else:
    60                     self.exc_info = (BadYieldError("yielded unknown object %r" % yielded),)
    61         finally:
    62             self.running = False

    到这里Runner类实例的run方法执行完毕,接下来执行到了Task类实例的start方法。

    5. 看看Task类实例的start方法的实现代码:

     1 def start(self, runner):
     2     # runner是一个Runner类实例
     3     self.runner = runner
     4         
     5     # object每次返回了不同的python的对象实例
     6     # 每次生成的对象的id都不一样,所以把它当作一个唯一的key
     7     self.key = object()
     8         
     9     # 调用runner的register_callback方法,以key作为参数
    10     # 该方法只有一句代码:self.pending_callbacks.add(key)
    11     # 在self.pending_callbacks这个set类型数据中,加入key
    12     runner.register_callback(self.key)
    13         
    14     # 加入callback,以key作为参数
    15     # result_callback是一个装饰器
    16     # 返回的是内层函数
    17     # !!!下面的self.func执行成功后,会调用这个返回的内层函数!!!
    18     self.kwargs["callback"] = runner.result_callback(self.key)
    19     
    20     # 执行真正的函数,示例代码中执行的就是http_client.fetch函数
    21     # self.kwargs中已经加入了一个关键字参数:'callback'
    22     self.func(*self.args, **self.kwargs)

    6. 负责返回内容的回调函数,就是Runner类实例的result_callback方法,它是一个装饰器。

     1 # 外层函数接受key作为参数
     2 def result_callback(self, key):
     3     # 内层函数负责返回内容
     4     # 由http_client.fetch函数调用
     5     # result就是fetch函数返回的内容
     6     def inner(*args, **kwargs):
     7         if _DEBUG:
     8             pdb.set_trace()
     9         if kwargs or len(args) > 1:
    10             result = Arguments(args, kwargs)
    11         elif args:
    12             result = args[0]
    13         else:
    14             result = None
    15         #最后调用Runner类实例的set_result方法
    16         self.set_result(key, result)
    17     return inner

    7. 将返回的内容添加到Runner类实例的result字典中

    def set_result(self, key, result):
        """Sets the result for ``key`` and attempts to resume the generator."""
        # 将内容加入到self.results字典中
        self.results[key] = result
        #最后调用一遍run
        self.run()
  • 相关阅读:
    ASP.NET AJAX 's UpdatePanel 中使用Page.ClientScript.RegisterStartupScript 失败
    从struct到byte[]之RawFormatter
    一个HTTP.二进制POST和HTTP指定长度接收的C++实现
    发现移动太NB了,验证码图像路径直接包含验证码
    又一个网页下载者木马
    Trojan program TrojanDownloader.JS.IstBar.ai 病毒样本
    用控件的方式解决问题在客户端关联WEB控件引用
    需要防范的XSS攻击
    用"指定字符组成的任意进制"转换生成不带4的卡号
    人为漏洞的构造、文件的载入、验证机制的突破
  • 原文地址:https://www.cnblogs.com/huazi/p/2812146.html
Copyright © 2011-2022 走看看