发布延迟引用:重点是什么

时间:2014-12-16 06:36:56

标签: python twisted

Twisted tutorial中提到了这种模式作为避免两次延迟相同的标准方法:

class A(ClientFactory):
...
def finished(self, result):
  if self.deferred is not None:
    d, self.deferred = self.deferred, None
    d.callback(result)

但根据我的理解,Deferred不会让你(即如果你这样做会引发异常)两次调用同一个实例。那么为什么要在任何地方重复这个代码来重新创建这种安全机制呢?

1 个答案:

答案 0 :(得分:1)

正如您所说,第二次拨打Deferred.callback会提出AlreadyCalledError。但是编写不会触发此异常的代码是惯用的。我编写或读取的大多数使用延迟的代码都会尝试保留AlreadyCalledError来表示编程错误,而不是可以安全忽略的正常运行时条件。

所以:

def finished(self, result):
    try:
        self.deferred.callback(result)
    except AlreadyCalledError:
        pass

不是首选拼写。我认为这是因为可能还有其他原因引发AlreadyCalledError,而不仅仅是因为finished被多次调用而且这种错误处理会掩盖这些情况,可能会隐藏错误。< / p>

抛弃Deferred实例的第二个原因是帮助意外异常日志记录。当Deferred被垃圾收集并且它有一个未处理的Failure结果时,会记录此Failure。这通常表示编程错误。 Deferred只要有任何引用就不能进行垃圾收集,所以在工厂丢弃引用至少可以确保工厂不会保持Deferred活着(虽然使用Deferred的应用程序代码仍然可以。)

另一种方法是写下这个:

def finished(self, result):
    if self.deferred is not None:
        self.deferred.callback(result)
        self.deferred = None

这仍然会丢弃Deferred但没有元组拆包。但是,这里有一个问题。

考虑更完整版的A

class A(ClientFactory):
    def waitUntilFinished(self):
        self.deferred = Deferred()
        return self.deferred

    def finished(self, result):
        if self.deferred is not None:
            d, self.deferred = self.deferred, None
            d.callback(result)

正如您所看到的,在A.waitUntilFinished发生一次之前调用finished两次是不安全的。也就是说,如果你写:

a = A()
x = a.waitUntilFinished()
y = a.waitUntilFinished()

然后你可以非常肯定x永远不会收到结果。这可能很难过,但您可以合理地将其记录为API的限制。

现在,请考虑这种略有不同的使用模式:

a = A()
x = a.waitUntilFinished()

def doSomething(result):
    return a.waitUntilFinished()
x.addCallback(doSomething)

此代码在调用waitUntilFinished之前不再调用finished两次。在第二次调用finished之前,它会等到waitUntilFinished被调用。如果您将API记录为只能安全地调用一次,直到产生的延迟触发,有人可能会认为这种用法是合理的。

更简单的实施:

def finished(self, result):
    if self.deferred is not None:
        self.deferred.callback(result)
        self.deferred = None

有问题。 doSomething会调用self.deferred.callback(result)。更具体地说,doSomethingself.deferred.callback返回之前被调用(换句话说,doSomething由该语句同步调用)。 doSomething调用waitUntilFinished来创建新的Deferred并将其分配给工厂的deferred属性。然后self.deferred.callback(result)结束并且self.deferred =无runs - and that new延迟`被抛弃,永远不会被调用。

通过在调用其回调链之前将self.deferred设置为None ,可以避免这种情况。