首页 > 解决方案 > Twisted 中的合作 - 产生执行上下文

问题描述

下面是我的测试平台

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语句将使函数“产生执行上下文”(无论这在 Twisted 中意味着什么)并让另一个延迟来接管执行。对于那个特定的例子,我希望main()开始执行,调用foo(),它被包装inlineCallbacks成一个deferred,然后产生让foo()最终开始的执行。然后foo()反过来也产生执行,所以最终打印行的顺序应该是

after yield in main
after yield in foo

由于某种原因,输出是

after yield in foo
after yield in main

在 Twisted 中实现协作多任务并让执行上下文转到另一个延迟队列的正确方法是什么?

标签: pythontwisted

解决方案


我预计 yield 语句将使函数“产生执行上下文”(无论这在 Twisted 中意味着什么),并让另一个 deferred 来接管执行。

根据您的观察,我认为您现在已经摆脱了这种期望。非常清楚,这不是yieldinlineCallbacks.

在这样的函数中yield所做的是将一个值传递给蹦床。如果该值为 Deferred,则 trampoline 会暂停生成器的执行,直到 Deferred 触发。如果该值不是 Deferred,或者当 Deferred 使用一个值触发时,生成器将使用发送给它的该值恢复。

因此,既然yieldis 相同yield NoneNone不是 a Deferred,这些yield表达式只是一种昂贵的表达方式None

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

在 Twisted 中实现协作多任务并让执行上下文转到另一个延迟队列的正确方法是什么?

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

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

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

Twisted 提供的一个替代方案是试图解决其中的一些困难,twisted.internet.task.cooperateinlineCallbacks倾向于使用裸发电机。这些实际上确实用于yield提供暂停点,但这样做的方式不会让整个反应堆有机会在恢复之前运行。这解决了一些 CPU 问题和可能的一些维护困难,尽管最终它仍然引入了一些基于时间的依赖关系。但是,至少可以在涉及的情况下对裸生成器进行操作,cooperate这在一定程度上减轻了这种困难(与inlineCallbacks惯用编写的代码破坏底层生成器并仅提供返回Deferred用于测试的函数的情况相比)。


推荐阅读