执行周期性操作
我在使用Windows系统。我想每10秒钟执行一次一个叫foo()
的函数。
我该怎么做呢?
8 个回答
我很惊讶没有找到使用生成器来计时的解决方案。所以我自己设计了一个。
这个解决方案是:单线程的,每个周期不创建对象,使用生成器来处理时间,计时非常准确,精度和time
模块一样(和我在Stack Exchange上尝试过的几个解决方案不一样)。
注意:如果你用的是Python 2.x,把下面的next(g)
换成g.next()
。
import time
def do_every(period,f,*args):
def g_tick():
t = time.time()
while True:
t += period
yield max(t - time.time(),0)
g = g_tick()
while True:
time.sleep(next(g))
f(*args)
def hello(s):
print('hello {} ({:.4f})'.format(s,time.time()))
time.sleep(.3)
do_every(1,hello,'foo')
比如说,结果是:
hello foo (1421705487.5811)
hello foo (1421705488.5811)
hello foo (1421705489.5809)
hello foo (1421705490.5830)
hello foo (1421705491.5803)
hello foo (1421705492.5808)
hello foo (1421705493.5811)
hello foo (1421705494.5811)
hello foo (1421705495.5810)
hello foo (1421705496.5811)
hello foo (1421705497.5810)
hello foo (1421705498.5810)
hello foo (1421705499.5809)
hello foo (1421705500.5811)
hello foo (1421705501.5811)
hello foo (1421705502.5811)
hello foo (1421705503.5810)
注意,这个例子模拟了CPU在每个周期内做其他事情0.3秒。如果你每次都改成随机时间,也没关系。yield
行中的最大值是为了防止sleep
出现负数,万一被调用的函数执行时间超过了指定的周期。在这种情况下,它会立即执行,并在下次执行的计时中弥补损失的时间。
简单来说,如果你只是让程序睡10秒钟,或者用 threading.Timer(10,foo)
这种方式,可能会导致时间不准确的问题。(这可能对你来说无所谓,但在某些情况下,这可能会引发不少麻烦。)造成这种情况的原因主要有两个:一个是你线程醒来的时间不准确,另一个是你函数执行的时间不准确。
在这篇文章的最后你可以看到一些结果,但首先我会给你一个修复的例子。你需要记录下你的函数应该被调用的时间,而不是它实际被调用的时间,并且要考虑这两者之间的差异。
下面是一个会稍微漂移的版本:
import datetime, threading
def foo():
print datetime.datetime.now()
threading.Timer(1, foo).start()
foo()
它的输出看起来是这样的:
2013-08-12 13:05:36.483580
2013-08-12 13:05:37.484931
2013-08-12 13:05:38.485505
2013-08-12 13:05:39.486945
2013-08-12 13:05:40.488386
2013-08-12 13:05:41.489819
2013-08-12 13:05:42.491202
2013-08-12 13:05:43.492486
2013-08-12 13:05:44.493865
2013-08-12 13:05:45.494987
2013-08-12 13:05:46.496479
2013-08-12 13:05:47.497824
2013-08-12 13:05:48.499286
2013-08-12 13:05:49.500232
你可以看到,毫秒的计数一直在增加,因此,开始时间在“漂移”。
这是一个能正确处理漂移的代码:
import datetime, threading, time
next_call = time.time()
def foo():
global next_call
print datetime.datetime.now()
next_call = next_call+1
threading.Timer( next_call - time.time(), foo ).start()
foo()
它的输出看起来是这样的:
2013-08-12 13:21:45.292565
2013-08-12 13:21:47.293000
2013-08-12 13:21:48.293939
2013-08-12 13:21:49.293327
2013-08-12 13:21:50.293883
2013-08-12 13:21:51.293070
2013-08-12 13:21:52.293393
在这里你可以看到,毫秒的时间不再增加了。
如果你的事件发生得非常频繁,你可能想在一个线程中运行计时器,而不是为每个事件都启动一个新线程。处理漂移的代码可以这样写:
import datetime, threading, time
def foo():
next_call = time.time()
while True:
print datetime.datetime.now()
next_call = next_call+1;
time.sleep(next_call - time.time())
timerThread = threading.Thread(target=foo)
timerThread.start()
不过,你的应用程序不会正常退出,你需要手动结束计时器线程。如果你想在应用程序完成时正常退出,而不手动结束线程,你应该使用
timerThread = threading.Thread(target=foo)
timerThread.daemon = True
timerThread.start()
在foo()
的最后,创建一个Timer
,让它在10秒后再调用一次foo()
。
因为Timer
会创建一个新的thread
(线程)来调用foo()
。
这样你就可以继续做其他事情,而不会被阻塞。
import time, threading
def foo():
print(time.ctime())
threading.Timer(10, foo).start()
foo()
#output:
#Thu Dec 22 14:46:08 2011
#Thu Dec 22 14:46:18 2011
#Thu Dec 22 14:46:28 2011
#Thu Dec 22 14:46:38 2011