【问题标题】:How to advance clock and going through all the events如何提前时钟并完成所有事件
【发布时间】:2015-08-05 14:44:45
【问题描述】:

出于测试目的阅读此answer(第2点)到与Twistedtask.Clock相关的问题,我发现没有办法将时钟从t0提前到t1很奇怪同时捕获t0t1 中的所有callLater 调用。

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

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,这对于从概率分布中采样时间的callLater 调用来说可能非常棘手。

谁能想到解决这个问题的办法?

【问题讨论】:

    标签: python twisted trial


    【解决方案1】:

    这是一个函数,它将通过迭代 reactor.getDelayedCalls 将反应器推进到下一个 IDelayedCall。这有 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!" 语句,但随后将永远挂起,因为回调链永远不会完成。

    【讨论】:

      【解决方案2】:

      我发现非常奇怪的是,在捕获 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,您可以使时间步长比您在典型 使用真正的反应器。

      但是,对于真正的反应器来说,很可能会出现大量时间都在一个离散步骤中流逝的情况。这可能是因为系统时钟发生了变化(有一些讨论可以独立于系统时钟来安排事件,以便这种情况消失)或者可能是因为某些应用程序代码阻塞了反应器一段时间(实际上,应用程序代码总是阻塞反应堆!但在典型的程序中它只会阻塞它一段足够短的时间让大多数人忽略)。

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

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

      关于迈克的建议,即准确地提前到下一个预定的通话,您可以轻松地做到这一点,而无需破解任何内部结构。 clock.advance(clock.getDelayedCalls()[0].getTime() - clock.seconds()) 会做到这一点(也许你会争辩 Clock 会更好,如果它至少为此提供了一个明显的辅助函数来简化对常见情况的测试)。请记住,真正的时钟不会像这样提前,所以如果您的代码在使用此技巧时在单元测试中具有某种理想的行为,请不要误以为这意味着相同的理想行为会存在于实际使用中。

      【讨论】:

      • +1 感谢您的澄清。我的问题是关于您在回答的最后一段中评论的方向:perhaps you could argue Clock would be better if it at least offered an obvious helper function for this to ease testing of the common case。我使用您和@Mike Lutz 的建议将时钟提前给定时间,而不会跳过延迟的呼叫,也不会尝试编写伪连续时钟。
      • 顺便说一句,我已经浏览了 task.clock.advance(在 Twisted 版本 15.1.0 中)方法。似乎它已经运行了所有延迟的调用......似乎stackoverflow.com/a/14627291/1336939 中的语句(2)指的是旧版本(可能是twistedmatrix.com/trac/browser/tags/releases/twisted-8.2.0/…
      • 它确实会在提前结束的时间间隔内运行所有调用。不过,我从你的问题中得知你想要的不止这些。我的答案顶部的示例是否与您的问题相关,或者我是否走上了一条不相关的道路?
      • 不,您的回答很有帮助,谢谢。我在问题中发布的方法advance_clock 被指示在时间间隔内运行延迟调用。我认为这不是 Twisted 完成的,也不知道 Clock 中保存了延迟呼叫列表。谢谢。
      【解决方案3】:

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

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

      我还没有做任何工作来确定这些列表是否暴露在任何地方,但如果你想把反应堆的生命掌握在自己手中,我相信你可以破解自己的方式。

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

      祝你好运

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-05-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-10-31
        • 2021-02-18
        • 2011-09-03
        相关资源
        最近更新 更多