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 exc_info = None try: value = future.result() except Exception: self.had_exception = True exc_info = sys.exc_info() if exc_info is not None: yielded = self.gen.throw(*exc_info) exc_info = None 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(_value_from_stopiteration(e)) 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
def wrapper(*args, **kwargs): future = _create_future() 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 = _value_from_stopiteration(e) except Exception: future_set_exc_info(future, sys.exc_info()) try: return future finally: # Avoid circular references future = None else: if isinstance(result, 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 = _create_future() 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_unless_cancelled( future, _value_from_stopiteration(e)) except Exception: future_set_exc_info(future, sys.exc_info()) else: _futures_to_runners[future] = Runner( result, future, yielded) yielded = None try: return future finally: # Subtle memory optimization: if next() raised an exception, # the future's exc_info contains a traceback which # includes this stack frame. This creates a cycle, # which will be collected at the next full GC but has # been shown to greatly increase memory usage of # benchmarks (relative to the refcount-based scheme # used in the absence of cycles). We can avoid the # cycle by clearing the local variable after we return it. future = None future_set_result_unless_cancelled(future, result) return future
def run(self): 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 exc_info = None try: value = future.result() except Exception: self.had_exception = True exc_info = sys.exc_info() if exc_info is not None: yielded = self.gen.throw(*exc_info) exc_info = None 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: 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
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) return future future.set_result(result) return future
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): 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
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 exc_info = None try: value = future.result() except Exception: self.had_exception = True exc_info = sys.exc_info() if exc_info is not None: yielded = self.gen.throw(*exc_info) exc_info = None else: # 发送异步调用结果,通知任务完成 yielded = self.gen.send(value) # self.gen为生成器,即被@gen.coroutine修饰的函数, # self.gen.send发送结果到 yield语句进行赋值,即 # result = yield clien.fetch(url),这里的result # 即为value,生成器结束会得到StopIteration异常结束 # 调用,否则继续调用self.handle_yield 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: # 捕捉到StopIteration 或者 Return 异常,说明 self.gen生成器 # 结束,Return 异常一般由用户调用 raise Return(True) 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) # 如果是 Return 异常设置返回值 self.result_future.set_result(_value_from_stopiteration(e)) 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 # 返回True说明yielded已经完成done,继续运行返回结果给上层,如果yielded # 还在等待Future完成,那么直接返回,handle_yield已经将yielded放到ioloop # 进行处理,完成后回调run运行 if not self.handle_yield(yielded): return finally: self.running = False
def wrapper(*args, **kwargs): future = _create_future() if replace_callback and 'callback' in kwargs: warnings.warn( "callback arguments are deprecated, use the returned Future instead", DeprecationWarning, stacklevel=2) 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 = _value_from_stopiteration(e) except Exception: future_set_exc_info(future, sys.exc_info()) try: return future finally: # Avoid circular references future = None else: if isinstance(result, 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 = _create_future() 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_unless_cancelled( future, _value_from_stopiteration(e)) except Exception: future_set_exc_info(future, sys.exc_info()) else: # Provide strong references to Runner objects as long # as their result future objects also have strong # references (typically from the parent coroutine's # Runner). This keeps the coroutine's Runner alive. # We do this by exploiting the public API # add_done_callback() instead of putting a private # attribute on the Future. # (Github issues #1769, #2229). runner = Runner(result, future, yielded) future.add_done_callback(lambda _: runner) yielded = None try: return future finally: # Subtle memory optimization: if next() raised an exception, # the future's exc_info contains a traceback which # includes this stack frame. This creates a cycle, # which will be collected at the next full GC but has # been shown to greatly increase memory usage of # benchmarks (relative to the refcount-based scheme # used in the absence of cycles). We can avoid the # cycle by clearing the local variable after we return it. future = None future_set_result_unless_cancelled(future, result) return future
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: if self.exc_info is None: try: if not self.future.done(): return next = self.future.result() self.future = None except Exception: self.exc_info = sys.exc_info() try: orig_stack_contexts = stack_context._state.contexts if self.exc_info is not None: self.had_exception = True exc_info = self.exc_info self.exc_info = None yielded = self.gen.throw(*exc_info) else: yielded = self.gen.send(next) 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): self.future = TracebackFuture() def start_yield_point(): try: yielded.start(self) if yielded.is_ready(): self.future.set_result(yielded.get_result()) else: self.yield_point = yielded except Exception: self.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()) else: self.exc_info = (BadYieldError( "yielded unknown object %r" % (yielded, )), ) finally: self.running = False
def wrapper(*args, **kwargs): future = TracebackFuture() # 一个future的跟踪对象,这个是关键 # 原来的func不支持callback的参数,修饰之后是可以使用的 # 下面的这个future好好读懂,然后好好理解传说中的future功能 if replace_callback and 'callback' in kwargs: callback = kwargs.pop('callback') # 就是future成功了,执行lambda future: callback(future.result())函数,参数就是这个future # 也就是把这个可能执行的语句放到IOLoop里面去,这个就是这个add_future的功能 IOLoop.current().add_future( future, lambda future: callback(future.result())) # 异步的HTTP和异步的IOStream也许返回的类型不一样, # 感觉两个异步的东西才是tornado的关键 try: # 尝试运行函数,这里会yield出来,所以不会被阻塞的 # 1. 返回一个Return 2. 返回一个StopIteration(这个不是很清楚??) # 3. 返回一个Exception?就是运行错误了 result = func(*args, **kwargs) except (Return, StopIteration) as e: # 到这里说明结果已经出来了,future的任务结束了 # ?StopIteration无法理解 result = getattr(e, 'value', None) except Exception: # 如果出错的话 # 运行出错了,这个时候依旧会添加到IOLoop._callbacks # 下面这句话执行这个future全部的回调 future.set_exc_info(sys.exc_info()) return future # 一但结果确定,直接返回一个future else: # 如果内部有yield,直接返回一个types.GeneratorType 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 # 这里是获取yield的结果的地方,一般还是一个future的东西 yielded = next(result) # 获取yield的结果,这里一般是一个future类型yield出来的东西 # 先忽略下面的出错处理 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: # bug了 future.set_exc_info(sys.exc_info()) else: # 返回一个future Runner(result, future, yielded) # 如果是一个Exception,这里记录到future,然后返回了 return future future.set_result(result) # 这个时候添加回调到了IOLoop return future
def wrapper(*args, **kwargs): # 这个 future 对象是用来存储 coroutine 执行完毕后的结果的。结果通常有 # 两种来源,一个是在 coroutine 中 yield Return 对象,另一种则是 # coroutine 作为 generator 执行完毕后 raise # StopIteration,并有结果数据存储在 StopIteration 这个异常对象中。 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 = _value_from_stopiteration(e) except Exception: future.set_exc_info(sys.exc_info()) return future else: if isinstance(result, 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(_value_from_stopiteration(e)) except Exception: future.set_exc_info(sys.exc_info()) else: # Runner 负责执行一个 coroutine,如果 coroutine 中的异步 # 操作已经完成,则 future 会被填充该操作的结果,否则 future # 会继续处于等待结果的状态,并紧接着在下一行代码返回给 # coroutine 的调用者。 Runner(result, future, yielded) try: return future finally: # Subtle memory optimization: if next() raised an # exception, the future's exc_info contains a traceback # which includes this stack frame. This creates a cycle, # which will be collected at the next full GC but has # been shown to greatly increase memory usage of # benchmarks (relative to the refcount-based scheme # used in the absence of cycles). We can avoid the # cycle by clearing the local variable after we return it. future = None future.set_result(result) return future
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: # 如果 run 方法已经在被执行,则返回。 return try: self.running = True while True: future = self.future if not future.done(): # Runner 对应的 coroutine yield # 的对象还没有准备好,则返回。 return self.future = None try: orig_stack_contexts = stack_context._state.contexts exc_info = None try: value = future.result() except Exception: # 如果执行过程中有抛出异常,则捕获之,并让 coroutine # 也抛出该异常。Python 中的 generator 在 2.5 版本后才 # 提供了 throw 方法来提供实现 coroutine # 的可能性,详情可以参考: # https://www.python.org/dev/peps/pep-0342/ self.had_exception = True exc_info = sys.exc_info() if exc_info is not None: # 如果有异常,让 coroutine 抛出。 yielded = self.gen.throw(*exc_info) exc_info = None else: # 如果一切正常,则将结果反馈给 coroutine。 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: # coroutine 的执行绪执行完毕,Runner 的任务也完成了。 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) # 从 Exception 对象中获得最终结果,并反馈给 # result_future,进而触发 result_future 的 done callbacks。 self.result_future.set_result(_value_from_stopiteration(e)) 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