Twisted 中的合作——yield 执行上下文

Cooperation in Twisted - yield the execution context

下面是我的测试平台

from twisted.internet import defer, task


@defer.inlineCallbacks
def foo():
    yield
    print 'after yield in foo'


@defer.inlineCallbacks
def main(reactor):
    d = foo()
    yield
    print 'after yield in main'
    yield d


task.react(main)

我预计 yield 语句将使函数变为 "yield the exeuction context"(无论这在 Twisted 中意味着什么)并让另一个延迟接管执行。对于那个特定的例子,我希望 main() 开始执行,调用 foo(),它被 inlineCallbacks 包装成 deferred,然后产生让 foo() 最终开始执行。然后 foo() 也执行,所以最终打印行的顺序应该是

after yield in main
after yield in foo

出于某种原因,输出是

after yield in foo
after yield in main

在Twisted中实现协同多任务并让执行上下文转到另一个deferred in line的正确方法是什么?

I exepect that the yield statement will make the function to "yield the exeuction context" (whatever that means in Twisted) and let another deferred to take over the execution.

根据您的观察,我认为您现在已经不再抱有这种期望了。非常清楚,这不是 yield 在由 inlineCallbacks.

装饰的生成器函数中所做的

yield 在此类函数中的作用是将值传递给蹦床。如果值为 Deferred,蹦床将暂停生成器的执行,直到 Deferred 触发。如果该值不是 Deferred,或者当 Deferred 触发一个值时,生成器将恢复并发送给它的值。

因此,由于 yieldyield None 相同并且 None 不是 Deferred,因此这些 yield 表达式只是一种昂贵的方式说 None.

这也让您了解如何实现您所说的目标。要暂停执行,yield a Deferred 在您希望恢复执行之前不会触发。

What is the right way to implement cooperative multi-tasking in Twisted and let the execution context go to another deferred in line?

有许多可能正确的方法可以做到这一点。具体细节更多取决于您的具体应用要求。

不幸的是,有一些容易解释的俗气答案可能看起来是正确的,但最终可能会导致性能不佳和维护成本高昂。例如,您可以暂停 "a reactor iteration"(在这种情况存在的范围内,这可能比您认为的要少)。为此,yield twisted.internet.task.deferLater(reactor, 0.0, lambda: None)。此表达式使 Deferred 从现在起不早于 "zero seconds" 触发,但也有它现在不触发 right 的约束。

但是,这让整个反应堆实现有机会在您的生成器恢复之前开始工作 - 即使没有其他工作要做。因此,您要为合作的可能性付出巨大的 CPU 代价。此外,它通过引入基于时间的调度交互使得功能难以测试。

Twisted 提供的一个替代方案是 twisted.internet.task.cooperate,它放弃了 inlineCallbacks 以支持裸生成器。这些实际上确实使用 yield 来提供暂停点,但这样做的方式不会给 whole 反应器一个在恢复之前 运行 的机会。这解决了一些 CPU 问题和可能的一些维护困难,但最终它仍然引入了一些基于时间的依赖性。但是,至少可以在不涉及 cooperate 的情况下对 裸生成器进行操作,这在一定程度上减轻了这种困难(将其与 inlineCallbacks 进行比较,其中惯用编写的代码破坏了底层生成器,只提供了一个 returns 和 Deferred 用于测试的功能。