意外的tornado.ioloop.PeriodicCallback行为

4
尝试弄清楚如何安排“PeriodicCallback”的计划,我写了这个脚本:
import time
import tornado.ioloop

t0 = time.time()

def foo():
    time.sleep(1)
    print(time.time() - t0)

tornado.ioloop.PeriodicCallback(foo, 2000).start()
tornado.ioloop.IOLoop.instance().start()

我原本期望它每2秒或3秒触发一次,这取决于tornado是否等待完成后再安排下一个事件。然而,我得到了以下结果:

3.00190114975
6.00296115875
10.0029530525
14.0029621124
18.0029540062
22.0050959587
26.0040180683
30.005161047
34.0053040981

这里发生了什么?
2个回答

6
即使在“后台”PeriodicCallback中,睡眠也不是tornado应用程序的好选择,因为它会阻塞IOLoop并阻止它适当地安排事情。如果您正在使用Tornado,则需要将所有长时间阻塞调用替换为非阻塞等效(或将它们移到其他线程)。将sleep替换为IOLoop.add_timeout,将网络操作替换为IOStream或其他异步库等。

感谢你在Tornado上所做的一切工作!我使用sleep来代替某些计算密集型操作,因此我认为这个例子很好地模拟了我的用例。顺带说一下 - 尽管dano解决了我的问题,但我仍然对为什么延迟恰好为4秒感到有兴趣。 - PattimusPrime
2
这看起来像是调度中的一个错误 - 当没有其他操作时,回调函数本身的运行时间可能会干扰调度代码。这类似于 https://github.com/tornadoweb/tornado/issues/947。 - Ben Darnell

4

我认为你只是看到了由于运行IOLoop时产生的延迟。例如,如果我将PeriodicCallback更改为每5秒运行一次,我会得到以下输出:

6.00595116615
12.0075321198
17.0060141087
22.0051832199
27.0067241192
32.0061450005
37.0066981316
42.0063281059
47.0067460537

这几乎是您所期望的。此外,为了回答您最初的问题,PeriodicCallback在执行休眠后会安排下一次回调运行(这直接源自Tornado源代码):

class PeriodicCallback(object):
    def __init__(self, callback, callback_time, io_loop=None):
        self.callback = callback
        if callback_time <= 0:
            raise ValueError("Periodic callback must have a positive callback_time")
        self.callback_time = callback_time
        self.io_loop = io_loop or IOLoop.current()
        self._running = False
        self._timeout = None

    def start(self):
        """Starts the timer."""
        self._running = True
        self._next_timeout = self.io_loop.time()
        self._schedule_next()

    def _run(self):
        if not self._running:
            return
        try:
            self.callback() # Your function runs here.
        except Exception:
            app_log.error("Error in periodic callback", exc_info=True)
        self._schedule_next()  # Schedule next run after calling self.callback

    def _schedule_next(self):
        if self._running:
            current_time = self.io_loop.time()
            while self._next_timeout <= current_time:
                self._next_timeout += self.callback_time / 1000.0
            self._timeout = self.io_loop.add_timeout(self._next_timeout, self._run) # The callback is actually scheduled with the ioloop here.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接