本文介绍了为什么使用 Deferred.result 获取 Twisted Deferred 对象的结果是错误的?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

据我所知,永远不要使用 Twisted Deferred 对象的 result 属性来访问 Deferred 的值.我怀疑这样做的原因是

  1. 访问时可能没有结果(加注AttributeError)
  2. 访问时的结果可能不是最终结果(即并非所有回调已运行)

是否存在适合访问 Deferred 结果的值的情况?有没有更好的方法来访问结果以将其分配给变量或稍后使用它而不向 Deferred 添加额外的回调?

解决方案

您的怀疑基本上是正确的.

访问时可能没有结果不可用(引发 AttributeError)

Deferred 的想法是一些工作正在发生并且不会在某个固定时间完成.完成后就会完成.幸运的是,当它完成时,Deferred 可以告诉你结果——这就是回调的用途.这意味着,使用回调,你无法知道工作何时完成.您可能过早检查并得到 AttributeError(或其他类型的失败,更多内容见下文).你可能检查得太晚了,浪费了一些时间.您可以通过反复检查和处理错误来修复"过早的情况.这称为轮询".Twisted 的大部分存在是为了消除执行轮询的需要,因为轮询通常是不可取的(它很昂贵).修复"过晚情况的唯一方法是更频繁地检查——这会使过早"情况变得更糟.

访问时的结果可能不是最终结果(即并非所有回调都已运行)

Deferred 提供的一个特性是它允许在事件驱动编程中组合.您可以让单元 A 执行某些任务并返回 Deferred.您可以让单元 B 执行某些任务并依赖于单元 A 并返回一些其他 Deferred.这种组合可能如下所示:

d = Deferred()d_a = unit_a()def a_finished(结果):d_b = unit_b(结果)d_b.addCallback(d_final.callback)d_a.addCallback(a_finished)

d.result 在这个例子的执行过程中取什么值?首先它是一个 AttributeError,然后它是 unit_b Deferred 被回调的任何值.在这种情况下,您不会暴露不完整或中间结果,也没有问题.但是,这种组合笨拙、冗长且容易失败(例如,它错过了错误传播和取消功能).

组合Deferred API 的简单、惯用、鼓励的方式是:

d = unit_a()d.addCallback(unit_b)

更易于编写、更易于阅读,并且您可以获得铁路式错误传播等功能.

在这种情况下,d.result 取什么值?首先它是一个AttributeError.然后,在unit_a完成后,d.result就是unit_b返回的Deferred.所以如果你在这里查看,你不仅没有最终结果,而且你甚至没有真正的价值,你有一个 Deferred 代替.只有在 unit_b 完成后,d 才会接受 unit_b 的真实结果.

还有其他可能的序列.它们来自其他 Deferred 使用模式,这些模式不像上面的那样受欢迎,但它们肯定是可能的.例如,您可能有一个实现:

d = unit_a()

然后让 d 暴露给你的代码.在那之后,您可能会继续执行以下操作:

d.addCallback(unit_b)

现在 d.resultAttributeErrorunit_a 的结果到 Deferred 到结果的 Deferredunit_b.以上不是 Deferred 的优秀使用,但正如您所看到的,这是完全可能的.如果你在这种情况下轮询 d.result,你必须猜测非Deferred 值是否是 unit_a 的结果> 或 unit_b.

是否存在适合访问 Deferred 结果的值的情况?

很难完全排除它.当然,在 Deferred 本身的测试套件中有一些直接访问,这些可能是合法的.有可能在其他一些与测试相关的工具中,以这种方式访问​​ result 属性可能是合适的 - 但理想情况下,即使这样也可以避免,或者测试库(例如 Twisted 的试验)将提供更好的工具用于编写这些类型的测试(例如,试验确实提供了successResultOffailureResultOfassertNoResult).除了这些情况之外,我认为您应该非常非常仔细地查看 result 属性的任何用法,并且您可能会发现使用回调有一个更好(更易于维护、更不脆弱)的解决方案.>

是否有更好的方法来访问结果以将其分配给变量或稍后使用它而不向 Deferred 添加额外的回调?

访问结果的更好方法是添加额外的回调.正如另一个答案所暗示的那样,inlineCallbacks 是一种使用回调的方法,它看起来不像使用回调,并且是许多人的首选.它允许您对本地进行简单的分配以检索结果,但实现仍使用 Deferred.addCallback 和相关 API.

From what I've read, one should never use the result attribute of a Twisted Deferred object to access the value of the Deferred. I suspect the reasoning for this is that either

  1. No result may not be available at the time of access (raisesAttributeError)
  2. The result may not be final at the time of access (i.e. not all callbacks have run)

Is there ever a situation where it is appropriate to access the value of the result of the Deferred? Is there a better way to access the result to assign it to a variable or use it later without adding additional callbacks to the Deferred?

解决方案

Your suspicions are essentially correct.

The idea of Deferred is that some work is happening and it won't be finished by some fixed time. It will be finished when it is finished. Fortunately, when it is finished, Deferred can tell you about the result - this is what callbacks are for. This means that, without using callbacks, you can't know when the work is finished. You might check too early and get an AttributeError (or failure of another sort, more on this below). You might check too late and have wasted some time. You can "fix" the too-early case by checking repeatedly and handling the error. This is called "polling". Large parts of Twisted exist to remove the need to perform polling because polling is typically undesirable (it's expensive). The only way to "fix" the too-late case is by checking more frequently - which makes the "too-early" case worse.

A feature Deferred offers is that it allows for composition in event-driven programming. You can have unit A which performs some task and returns a Deferred. You can have unit B which performs some task and depends on unit A and returns some other Deferred. Such composition could look like this:

d = Deferred()

d_a = unit_a()
def a_finished(result):
    d_b = unit_b(result)
    d_b.addCallback(d_final.callback)
d_a.addCallback(a_finished)

What values does d.result take on over the course of the execution of this example? First it is an AttributeError and then it is whatever value the unit_b Deferred is called back with. In this case, you have no incomplete or intermediate results being exposed and there's no problem. However, this composition is awkward, verbose, and failure-prone (it misses error propagation and cancellation features, for example).

The easy, idiomatic, encouraged way to compose Deferred APIs is:

d = unit_a()
d.addCallback(unit_b)

Easier to write, easier to read, and you get features like railroad-style error propagation.

What values does d.result take on in this case, though? First it is an AttributeError. Then, after unit_a is done, d.result is the Deferred returned by unit_b. So if you look it up here, not only do you not have the final result but you don't even have a real value, you have a Deferred instead. Only after unit_b is done does d take on the real result of unit_b.

There are other possible sequences as well. They arise from other Deferred usage patterns that aren't as preferred as the one above but they are certainly possible. For example, you might have an implementation which does:

d = unit_a()

and then have d exposed to your code. After that point you might have the implementation proceed to:

d.addCallback(unit_b)

Now d.result proceeds from AttributeError to the result of unit_a to a Deferred to the result of unit_b. The above is not excellent use of Deferred but as you can see, it is entirely possible. If you are polling d.result in this case, you have the complication of having to guess whether a non-Deferred value is the result of unit_a or of unit_b.

It's hard to completely rule it out. Certainly in the test suite for Deferred itself there are some direct accesses and these may be legitimate. It's possible that in some other test-related tools it may be appropriate to access the result attribute this way - but ideally even there it would be avoided or testing libraries (such as Twisted's trial) would provide better tools for writing these kinds of tests (eg trial does provide successResultOf, failureResultOf, and assertNoResult). Beyond those cases, I think you should look very, very carefully at any use of the result attribute and you will probably find that there is a better (more maintainable, less fragile) solution using callbacks.

The better way to access the result is always to add an additional callback. As another answer suggests, inlineCallbacks is a way to use callbacks that doesn't look like using callbacks and is preferred by many. It lets you do a simple assignment to a local to retrieve a result but the implementation is still using Deferred.addCallback and the related APIs.

这篇关于为什么使用 Deferred.result 获取 Twisted Deferred 对象的结果是错误的?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-28 16:03