如何为deferLater添加错误回调?
考虑一下下面这段使用了deferLater的twisted代码:
import random
from twisted.internet.task import deferLater
from twisted.internet import reactor
def random_exception(msg='general'):
if random.random() < 0.5:
raise Exception("Random exception with 50%% likelihood occurred in %s!" % msg)
def dolater():
random_exception('dolater')
print "it's later!"
def whoops(failure):
failure.trap(Exception)
print failure
defer = deferLater(reactor, 10, dolater)
defer.addErrback(whoops)
reactor.run()
在这段代码中,当程序在睡眠10秒的时候,出现了一个异常(也就是一个),但看起来KeyboardInterrupt
whoops
这个方法从来没有被调用。我猜测是因为我在deferred开始后才添加了errBack,所以它没有被正确注册。希望能得到一些建议。
编辑:
好吧,似乎没有人喜欢我用信号(而不是异常)KeyboardInterrupt
来表示defer之外的错误情况。我认真考虑过在defer回调中可能出现的实际异常,但没能想到特别好的例子,大多数情况都是某种信号(或者开发者的错误),所以目前用信号处理还可以——但这并不是我问题的核心。
根据我的理解,twisted的回调/错误回调系统会处理回调结构中的错误——比如说如果dolater
抛出了某种异常。为了说明这一点,我添加了一个可能在dolater
中发生的异常,来展示如果异常发生在dolater
中,errback可以很好地处理这个异常。
我担心的是,如果在反应器正常工作的情况下出现了问题,而我能想到的唯一问题就是键盘中断,那我希望whoops
能被触发。看起来如果我在反应器中放入其他异步事件并从那里抛出异常,那么dolater
的代码就不会受到影响,我需要为那些其他异步事件添加errbacks。整个twisted程序并没有一个统一的错误处理机制。
所以现在只能用信号,直到我能找到一种方法让反应器在没有信号的情况下失败。
3 个回答
Twisted是一个可以让你同时处理很多事情的库。它会尽量把这些事情分开处理(虽然毕竟还是Python,还是会有一些全局状态等问题)。
比如说,你有一个TCP服务器,两个客户端连接到这个服务器。如果其中一个客户端发送了一些错误的数据,导致你的解析器出现了bug并抛出了异常,这个异常不会影响到另一个客户端,也就是说,另一个客户端不会收到任何错误信息。希望你也是这么想的(至少不是自动的)。
同样的,如果你有一个客户端连接到你的服务器,并且你用deferLater
启动了一个延迟调用,如果这个客户端触发了那个bug,你也不希望这个错误被传递到deferLater
返回的Deferred
的错误回调中。
这里的意思是,不同的事件源通常是分开处理的(直到你写了一些代码把它们连接在一起)。
在你调用deferLater
和Twisted开始执行你传给deferLater
的函数之间的十秒钟内,发生的任何错误——包括你按下C-c让Python抛出一个键盘中断——都不会和这个延迟调用关联起来,也不会传递到你附加在它的Deferred
上的错误回调中。
只有在你的dolater
函数中抛出的异常,才会导致那个Deferred
的错误回调链开始执行。
问题出在你想要捕捉的异常上,具体来说,KeyboardInterrupt
不是 Exception
的子类,所以不能用它来捕捉。如果你把这一行:
failure.trap(Exception)
改成:
failure.trap(KeyboardInterrupt)
这样就能捕捉到了。关于Python的异常层级结构,可以在官方文档中找到更多信息:https://docs.python.org/2/library/exceptions.html
如果你提到的 KeyboardInterrupt
是指一个信号(比如 ctrl-c
或 SIGINT
等),那么你需要做的就是设置一个信号处理器,把你的 whoops
函数作为回调函数。
根据 @jean-paul-calderone 的两个之前的回答 twisted: 捕获 keyboardinterrupt 并正确关闭 和 twisted - 通过 KeyboardInterrupt 中断回调,我尝试了以下代码,我觉得这能满足你的需求:
def dolater():
print "it's later!"
def whoops(signal, stackframe):
print "I'm here because of signal number " + str(signal)
reactor.stop()
defer = task.deferLater(reactor, 10, dolater)
signal.signal(signal.SIGINT, whoops)
reactor.run()
这段代码会在接收到 SIGINT
信号时调用 whoops
。我在 whoops
里加了一个 reactor.stop()
,因为如果不加的话,反应器会一直运行。如果你真的希望在按下 ctrl-c
时它继续运行,可以把这个去掉。
注意:我没有明确展示如何在信号系统中触发 err-back,因为(至少根据我的理解)这并不完全符合 defer'ed
的使用方式。我想如果你能找到办法把 defer'ed
放进信号处理器里,你可以触发它的 errback,但我觉得这可能超出了 twisted 的预期用法,可能会导致一些意想不到的后果。