【问题标题】:Unable to cancel Twisted Deferred chain (AlreadyCalledError)无法取消 Twisted Deferred 链 (AlreadyCalledError)
【发布时间】:2019-10-17 00:35:38
【问题描述】:

每当某些链接的延迟(子)引发错误时,我都会尝试取消 Deferred 链(主)。 但我收到了AlreadyCalledError 并且连锁店继续工作。

代码如下:

from twisted.internet import defer

def asyncText(s, okCallback, errCallback):
  deferred = defer.Deferred()
  deferred.addCallbacks(okCallback, errCallback)
  if s.find('STOP')>=0:
    deferred.errback(ValueError('You are trying to print the unprintable :('))
  else:
    deferred.callback(s)
  return deferred

def asyncChain():
  texts = ['Hello StackOverflow,', 
           'this is an example of Twisted.chainDeferred()',
           'which is raising an AlreadyCalledError',
           'when I try to cancel() it.',
           'STOP => I will raise error and should stop the main deferred',
           'Best regards'
           ]

  mainDeferred= defer.Deferred()

  for text in texts: 
    def onSuccess(res):
      print('>> {}'.format(res))

    def onError(err):
      print('Error!! {}'.format(err))
      mainDeferred.cancel()

    d = asyncText(text, onSuccess, onError)
    mainDeferred.chainDeferred(d)

这是输出:

>>> asyncChain()
- Hello StackOverflow,
- this is an example of Twisted.chainDeferred()
- which is raising an AlreadyCalledError
- when I try to cancel() it.
Error!! [Failure instance: Traceback (failure with no frames): <class 'ValueError'>: You are trying to print the unprintable :(
]
- Best regards
Unhandled error in Deferred:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File ".../test.py", line 1, in asyncChain
    mainDeferred.chainDeferred(d)
  File ".../.venvs/p3/lib/python3.6/site-packages/twisted/internet/defer.py", line 435, in chainDeferred
    return self.addCallbacks(d.callback, d.errback)
  File ".../.venvs/p3/lib/python3.6/site-packages/twisted/internet/defer.py", line 311, in addCallbacks
    self._runCallbacks()
--- <exception caught here> ---
  File ".../.venvs/p3/lib/python3.6/site-packages/twisted/internet/defer.py", line 654, in _runCallbacks
    current.result = callback(current.result, *args, **kw)
  File ".../.venvs/p3/lib/python3.6/site-packages/twisted/internet/defer.py", line 501, in errback
    self._startRunCallbacks(fail)
  File ".../.venvs/p3/lib/python3.6/site-packages/twisted/internet/defer.py", line 561, in _startRunCallbacks
    raise AlreadyCalledError
twisted.internet.defer.AlreadyCalledError: 

我也尝试使用canceller,如下所示:

def asyncOnCancel(d):
  print('------ cancel() called ------')
  d.errback(ValueError('chain seems to be cancelled!'))
def asyncChainOnError(err):
  print('------ ERROR ON Chain {} ------'.format(err))

...
  mainDeferred= defer.Deferred(canceller= asyncOnCancel)
  mainDeferred.addErrback(asyncChainOnError)
...

但结果是一样的。

我还尝试过延迟孩子的.callback(s) 呼叫,或者在.chainDeferred() 之后呼叫他们。但我总是得到相同的行为。

  • 真的有可能取消链接的Deferred(并让链接的子节点延迟也被取消)吗?
  • 为什么我会收到这个AlreadyCalledError

我正在使用 python 3.6.6 和 Twisted 18.9.0。

谢谢!

******* 编辑 *******

Jean-Paul 回答之后,并注意到.chainDeferred() 不是我需要的,我会以更清晰的方式将我想要的(以及我最终是如何做到的)放在这里。

我想要的非常简单:以“同步”方式运行多个 Deferred(它们必须等待前一个完成),尽管它们不需要共享结果。如果一个失败,其余的都不会执行。

事实证明,使用 @defer.inlineCallbacksyield 很容易做到这一点。举个例子:

def asyncText(s):
  deferred = defer.Deferred()
  if s.find('STOP') >= 0:
    deferred.callback(True)
  else:
    deferred.callback(False)
  return deferred

@defer.inlineCallbacks
def asyncChain():
  texts = ['Hello StackOverflow,',
           'this is a simpler way to explain the question.',
           'I need no chainDeferred(). I need no .cancel().',
           'I just need inlineCallbacks decorator.',
           'STOP',
           'Yeah I will not be printed'
           ]
  for text in texts:
    stopHere = yield asyncText(text)
    if stopHere:
      break
    print('- {}'.format(text))

deferred= asyncChain()
>>> asyncChain()
- Hello StackOverflow,
- this is a simpler way to explain the question.
- I need no chainDeferred(). I need no .cancel().
- I just need inlineCallbacks decorator.

【问题讨论】:

    标签: python-3.x twisted twisted.internet


    【解决方案1】:

    目前尚不清楚您实际上要做什么,但我认为您的链条与您的预期相反:

        d = asyncText(text, onSuccess, onError)
        mainDeferred.chainDeferred(d)
    

    dasyncText 返回时已经触发。但是mainDeferred.chainDeferred(d) 的意思是“当 mainDeferred 触发时,将其结果传递给 d”。由于d 已经被解雇,这是无效的。 Deferred 只能触发一次。

    也许您的意思是d.chainDeferred(mainDeferred)。然后“当 d 触发时,将其结果传递给 mainDeferred”。

    但是,仍然存在一个问题,因为如果将d 链接到mainDeferred,那么在d 的回调中取消 mainDeferred 是没有意义的。结果将从d 传播到mainDeferred,因为它们是链式的。取消没有必要也没有用。

    【讨论】:

    • 明白,现在我看到chainDeferred 不是我需要的。我想要的是运行几个Deferred,以同步的方式(他们必须等待前一个完成),尽管他们不需要分享他们的结果。我看到here 可能我需要的只是到addCallback()mainDeferred,但我还没有让它工作。
    • 可能是f1().addCallback(lambda ignored: f2()) ...但也许接受这个答案并提出一个新问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-12-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多