我有一个Tornado的场景,我有一个从非协同调用或没有屈服的协程,但我需要传回异常。
想象一下以下方法:
@gen.coroutine
def create_exception(with_yield):
if with_yield:
yield exception_coroutine()
else:
exception_coroutine()
@gen.coroutine
def exception_coroutine():
raise RuntimeError('boom')
def no_coroutine_create_exception(with_yield):
if with_yield:
yield create_exception(with_yield)
else:
create_exception(with_yield)
通话:
try:
# Throws exception
yield create_exception(True)
except Exception as e:
print(e)
将正确引发异常。但是,以下都不会引发异常:
try:
# none of these throw the exception at this level
yield create_exception(False)
no_coroutine_create_exception(True)
no_coroutine_create_exception(False)
except Exception as e:
print('This is never hit)
后者是与我的问题类似的变体 - 我的控制之外的代码在不使用yield的情况下调用coroutines。在某些情况下,它们本身不是协同程序。无论哪种情况,都意味着它们产生的任何异常都会被吞噬,直到Tornado将其返回为"未来未收到异常。"
这与Tornado的意图相反,他们的文档基本上表明你需要在整个堆栈中执行yield / coroutine,以便它能够在没有hackery / trickery的情况下工作。
我可以改变引发异常的方式(即修改exception_coroutine
)。但我无法改变几种中间方法。
为了强制在整个Tornado堆栈中引发异常,我能做些什么,即使它没有正确地产生?基本上是在最后三种情况下适当地提出异常?
这很复杂,因为我无法更改导致此情况的代码。我只能在上面更改exception_coroutine
。
答案 0 :(得分:1)
你在Python中要求的是不可能的,因为在协程完成之后,调用函数决定yield
是否yielded
。协程必须在不引发异常的情况下返回,因此它可以是Future
,之后如果yielded
不是{{2016_08_13_001252_create_roles_table.php
,它就不再可能将异常引发到调用方的上下文中1}}。
你可以做的最好的事情就是检测Future的垃圾收集,但这除了log之外什么也做不了(这就是“未检索到的未来异常”消息的工作原理)
答案 1 :(得分:-1)
如果你很好奇为什么这不起作用,那是因为no_coroutine_create_exception包含一个yield语句。因此它是一个生成器函数,调用它不执行它的代码,它只创建一个生成器对象:
>>> no_coroutine_create_exception(True)
<generator object no_coroutine_create_exception at 0x101651678>
>>> no_coroutine_create_exception(False)
<generator object no_coroutine_create_exception at 0x1016516d0>
上面的调用都没有执行任何Python代码,它只创建必须迭代的生成器。
你必须创建一个阻止函数来启动IOLoop并运行它直到你的协程完成:
def exception_blocking():
return ioloop.IOLoop.current().run_sync(exception_coroutine)
exception_blocking()
(IOLoop充当多个非阻塞任务的调度程序,gen.coroutine
装饰器负责迭代协程直到完成。)
但是,我认为我很可能会回答你的问题,但只是让你继续沿着非生产性的道路前进。你几乎肯定最好不要使用异步代码或阻止代码而不是试图混合它们。