如何推进时钟并完成所有活动

时间:2015-05-23 18:04:00

标签: python twisted trial

为了测试目的,请将此answer(第2点)读到与Twisted的{​​{1}}相关的问题,我发现很奇怪没有办法推进task.Clockt0t1内捕获所有callLater次来电时{1}} t0。{/ p>

当然,您可以通过以下方式解决此问题:

t1

但是我们已经将问题转移到选择足够小的clock = task.Clock() reactor.callLater = clock.callLater ... def advance_clock(total_elapsed, step=0.01): elapsed = 0 while elapsed < total_elapsed: clock.advance(step) elapsed += step ... time_to_advance = 10 # seconds advance_clock(time_to_advance) ,这对于从概率分布中抽样时间的step调用来说可能非常棘手。

有人能想出解决这个问题的方法吗?

3 个答案:

答案 0 :(得分:3)

  

我发现非常奇怪的是,在t0和t1内捕获所有callLater调用时,无法将时钟从t0推进到t1。

根据您在问题中稍后所写的内容,我将假设您指出的案例是以下示例程序演示的案例:

from twisted.internet.task import Clock

def foo(reactor, n):
    if n == 0:
        print "Done!"
    reactor.callLater(1, foo, reactor, n - 1)

reactor = Clock()
foo(reactor, 10)
reactor.advance(10)

有人可能希望这个程序打印Done!,但事实并非如此。如果最后一行替换为:

for i in range(10):
    reactor.advance(1)

然后生成的程序 打印Done!

Clock以这种方式工作的原因在于它与真实时钟的工作方式完全相同。据我所知,没有使用连续时间系统的计算机时钟。我不会说不可能在一个具有不连续步骤的时钟上实现一个定时事件系统,这样它似乎可以提供连续的时间流 - 但我会说Twisted没有尝试这样做。

Clock与真正的反应堆实施之间唯一真正的区别在于Clock可以使时间步长远大于你在< em>典型的真实反应堆的使用。

然而,真正的反应堆很可能进入一个非常大的时间都在一个不连续的步骤中通过的情况。这可能是因为系统时钟发生了变化(有一些讨论使得可以独立于系统时钟安排事件以便这种情况消失)或者可能是因为某些应用程序代码阻塞了反应堆一段时间(实际上,应用程序代码总是阻止了反应堆!但在典型的程序中,它只会阻塞它一段时间,足以让大多数人忽略它。)

Clock提供一种模仿这些大步骤的方法,可以在出现其中一种情况时为您的程序编写测试。例如,也许你真的关心的是,当内核决定不安排你的程序2.1秒时,因为Linux I / O电梯算法中有一个奇怪的怪癖,你的物理引擎仍会计算2.1秒的物理,即使你的420次调用你的已跳过200Hz模拟循环。

可能公平地说,Twisted提供的默认(仅限标准?)基于时间的测试工具应该对常见情况更加友好......或者不是。也许这会鼓励人们编写只在常见情况下工作的程序,并在不常见(但最终不可避免)的情况出现时打破现实世界。我不确定。

关于迈克建议完全推进到下一个预定的电话会议,你可以轻松地做到这一点,而不会破坏任何内部。 clock.advance(clock.getDelayedCalls()[0].getTime() - clock.seconds())会做到这一点(也许你可以说Clock如果它至少提供了一个明显的辅助功能,以便于测试常见情况,那就更好了。请记住,真正的时钟这样前进,所以如果你的代码在你使用这个技巧的单元测试中有一定的理想行为,不要被愚弄认为这意味着同样的理想行为将存在于实际使用中。

答案 1 :(得分:1)

鉴于Twisted的典型用法类是混合硬件事件和定时器,我很困惑你为什么要这样做,但是......

我的理解是,Twisted正在通过reactor对象内部的一些列表来跟踪callLater事件(参见:http://twistedmatrix.com/trac/browser/tags/releases/twisted-15.2.0/twisted/internet/base.py#L437 - 类ReactorBase中的xxxTimedCalls列表)

我还没有做任何工作来弄清楚这些清单是否暴露在任何地方,但如果你想让反应堆生活在自己的手中,我相信你可以破解你的方式。

通过访问时间列表,您可以简单地将时间转发到列表的下一个元素是......但是如果您尝试测试与IO事件交互的代码,我无法想象这将会做任何事情但是让你迷惑......

祝你好运

答案 2 :(得分:0)

这是一个通过迭代IDelayedCall将反应堆前进到下一个reactor.getDelayedCalls的功能。这是Mike提到的不捕获IO事件的问题,因此您可以指定应等待的最短和最大时间,以及最大时间步长。

def advance_through_delayeds(reactor, min_t=None, max_t=None, max_step=None):
    elapsed = 0
    while True:
        if max_t is not None and elapsed >= max_t:
            break
        try:
            step = min(d.getTime() - reactor.seconds() for d in reactor.getDelayedCalls())
        except ValueError:
            # nothing else pending
            if min_t is not None and elapsed < min_t:
                step = min_t - elapsed
            else:
                break
        if max_step is not None:
            step = min(step, max_step)
        if max_t is not None:
            step = min(step, max_t-elapsed)
        reactor.advance(step)
        elapsed += step
    return elapsed

如果您需要等待一些I / O完成,则将min_tmax_step设置为合理的值。

# wait at least 10s, advancing the reactor by no more than 0.1s at a time
advance_through_delayeds(reactor, min_t=10, max_step=0.1)

如果设置了min_t,则getDelayedCalls在到达该时间后将返回一个空列表。

始终将max_t设置为合理值以防止测试套件挂起可能是个好主意。例如,在JPC的上述foo函数上,它确实到达了print "Done!"语句,但是由于回调链从未完成,因此它将永远挂起。