在 @inlineCallbacks-decorated 函数中未处理的异常不会触发 Errback

Errback not being fired on unhandled exception within @inlineCallbacks-decorated function

from twisted.internet import reactor
from twisted.internet.defer import inlineCallbacks


def fail():
  raise Exception()

@inlineCallbacks
def foo():
  yield reactor.callLater(5.0, fail)

def dump(*args, **kwargs):
  print 'dump', args, kwargs

d = foo()
d.addErrback(dump)

reactor.run()

这里我们有一个虚拟函数 foo(),它产生一个将在 5 秒内触发的 Deferred。当它触发时,会抛出一个异常,我希望它会被与我的顶级 Deferred 对象关联的 errback 捕获:

  1. foo() 被调用并立即 returns 延迟。我们向它添加一个 errback,它只打印出它的参数。
  2. 几秒钟后,反应器调用 fail()
  3. fail() 抛出异常。
  4. 异常是 "thrown" 在生成器生成时进入生成器。 Documentation:

The generator will be sent the result of the Deferred with the 'send' method on generators, or if the result was a failure, 'throw'.

  1. 异常未在生成器中捕获,因此 foo() 的 Deferred 应该调用其 errback:

Your inlineCallbacks-enabled generator will return a Deferred object, which ... will fail with a failure object if your generator raises an unhandled exception

  1. 在调用errback时,应该调用dump()

改为:

Unhandled Error
Traceback (most recent call last):
  File "untitled", line 19, in <module>
    reactor.run()
  File "/Library/Python/2.7/site-packages/twisted/internet/base.py", line 1192, in run
    self.mainLoop()
  File "/Library/Python/2.7/site-packages/twisted/internet/base.py", line 1201, in mainLoop
    self.runUntilCurrent()
--- <exception caught here> ---
  File "/Library/Python/2.7/site-packages/twisted/internet/base.py", line 824, in runUntilCurrent
    call.func(*call.args, **call.kw)
  File "untitled", line 6, in fail
    raise Exception()
exceptions.Exception:

为了进一步测试这一点,我尝试了 (a) 直接在 foo() 中引发异常,以及 (b) 尝试捕获 fail()foo() 中抛出的异常。

(a) 工作正常,调用了我的 errback。

(b) 不起作用,并导致同样的问题:

@inlineCallbacks
def foo():
  try:
    yield reactor.callLater(5.0, fail)
  except Exception, e:
    print e 

这是 Twisted 15.1.0 和 Python 2.7.10。

啊,reactor.callLater 没有 return Deferred。你可以让它与

一起工作
def fail(_):
  raise Exception()

@inlineCallbacks
def foo():
  d = Deferred()
  d.addCallback(fail)
  reactor.callLater(1.0, d.callback, None)
  yield d

您也可以使用 deferLater,因为这会 return 成为您想要的 Deferred

from twisted.internet import task, defer, reactor

def fail(*args):
    raise Exception()

def error(*args):
    print(args)

def dump(*args, **kwargs):
    print('dump', args, kwargs)

@defer.inlineCallbacks
def foo():
    d = task.deferLater(reactor, 1.0, fail)
    d.addErrback(error)
    yield d

D = foo()
D.addCallback(dump)
reactor.run()