【问题标题】:How can I raise an exception through Tornado coroutines incorrectly called?如何通过错误调用的 Tornado 协程引发异常?
【发布时间】:2016-08-11 14:25:35
【问题描述】:

我有一个使用 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。在某些情况下,它们本身并不是协程。无论哪种情况,这意味着它们生成的任何异常都会被吞噬,直到 Tornado 将它们作为“未收到未来异常”返回。

这与 Tornado 的意图完全相反,他们的文档基本上表明您需要在整个堆栈中执行 yield/coroutine 才能使其按照我想要的方式工作,而无需黑客/诡计。

我可以更改引发异常的方式(即修改exception_coroutine)。但是我不能改变几个中间方法。

我可以做些什么来强制在整个 Tornado 堆栈中引发异常,即使它没有正确产生?基本上是在最后三种情况下正确引发异常?

这很复杂,因为我无法更改导致这种情况的代码。我只能在上面更改exception_coroutine

【问题讨论】:

  • 两个问题。首先,你能举一个例子或调用代码的草图吗?我很难理解你的意图。其次,您是否希望调用方阻塞直到协程完成(正常或通过引发异常)?
  • @A.JesseJiryuDavis 上面的存根函数反映了它是如何被调用的。关于 2,请参阅我的编辑 - 我想在我的 try/except 中查看异常。

标签: python python-2.7 asynchronous tornado


【解决方案1】:

您所要求的在 Python 中是不可能的,因为在协程完成后调用函数会决定是否使用 yield。协程必须在不引发异常的情况下返回,因此它可以是yielded,之后如果Future 不是yielded,它就不再可能在调用者的上下文中引发异常。

你能做的最好的就是检测 Future 的垃圾回收,但这只能做日志(这就是“未检索到未来异常”消息的工作原理)

【讨论】:

    【解决方案2】:

    如果你好奇为什么这不起作用,那是因为 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 装饰器负责迭代协程直到完成。)

    但是,我想我很可能会回答您的直接问题,但只是让您走上一条没有成效的道路。几乎可以肯定,你最好在整个过程中使用异步代码或阻塞代码,而不是尝试混合它们。

    【讨论】:

    • 我无法更改底层调用结构(我认为这在问题中很明确?),所以虽然这是出于好意,但它并没有帮助。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-02-19
    • 1970-01-01
    • 1970-01-01
    • 2017-08-29
    • 1970-01-01
    • 2011-07-22
    • 2020-10-16
    相关资源
    最近更新 更多