如何在Python中定期运行一个函数
我有一个简单的节拍器运行,由于某种原因,当它在一个较低的Bpm是好的,但在更高的BPM它是不一致的,并不稳定。 我不知道发生了什么事。 我想尝试使用某些东西来定期运行它。 有没有办法做到这一点?
这是我的代码:
class thalam(): def __init__(self,root,e): self.lag=0.2 self.root=root self.count=0 self.thread=threading.Thread(target=self.play) self.thread.daemon=True self.tempo=60.0/120 self.e=e self.pause=False self.tick=open("tick.wav","rb").read() self.count=0 self.next_call = time.time() def play(self): if self.pause: return winsound.PlaySound(self.tick,winsound.SND_MEMORY) self.count+=1 if self.count==990: self.thread=threading.Thread(target=self.play) self.thread.daemon=True self.thread.start() return self.next_call+=self.tempo new=threading.Timer(self.next_call-time.time(),self.play) new.daemon=True new.start() def stop(self): self.pause=True winsound.PlaySound(None,winsound.SND_ASYNC) def start(self): self.pause=False def settempo(self,a): self.tempo=a class Metronome(Frame): def __init__(self,root): Frame.__init__(self,root) self.first=True self.root=root self.e=Entry(self) self.e.grid(row=0,column=1) self.e.insert(0,"120") self.play=Button(self,text="Play",command=self.tick) self.play.grid(row=1,column=1) self.l=Button(self,text="<",command=lambda:self.inc("l")) self.l.grid(row=0,column=0) self.r=Button(self,text=">",command=lambda:self.inc("r")) self.r.grid(row=0,column=2) def tick(self): self.beat=thalam(root,self.e) self.beat.thread.start() self.play.configure(text="Stop",command=self.notick) def notick(self): self.play.configure(text="Start",command=self.tick) self.beat.stop() def inc(self,a): if a=="l": try: new=str(int(self.e.get())-5) self.e.delete(0, END) self.e.insert(0,new) self.beat.settempo(60.0/(int(self.e.get()))) except: print "Invalid BPM" return elif a=="r": try: new=str(int(self.e.get())+5) self.e.delete(0, END) self.e.insert(0,new) self.beat.settempo((60.0/(int(self.e.get())))) except: print "Invalid BPM" return
由于需要处理器与其他程序共享,做任何需要时间精度的事情是非常困难的。 不幸的是,对于计时关键程序,操作系统可以随时切换到另一个进程,只要它select。 这可能意味着它可能不会返回到您的程序,直到明显的延迟。 在导入时间后使用time.sleep是一种更加一致的方式,可以平衡嘟嘟声之间的时间,因为处理器没有什么“理由”可以切换。 虽然Windows上的睡眠有15.6ms的默认粒度,但是我认为你不需要在64Hz的频率下打一个节拍。 此外,看起来您正在使用multithreading来尝试解决您的问题,但是,线程的python实现有时会强制线程顺序运行。 这使得切换离开你的过程更加糟糕。
我觉得最好的解决办法是以所需的频率产生包含节拍器嘟嘟声的声音数据 。 然后你可以用操作系统理解的方式播放声音数据。 由于系统知道如何以可靠的方式处理声音,您的节拍器就可以工作。
很抱歉,令人失望,但时间关键的应用程序是非常困难的,除非你想与你正在使用的系统弄脏你的手。
播放声音以模仿普通节拍器不需要“实时”function。
看起来你使用Tkinter框架来创buildGUI。 root.after()
允许你延迟调用一个函数 。 你可以用它来实现滴答:
def tick(interval, function, *args): root.after(interval - timer() % interval, tick, interval, function, *args) function(*args) # assume it doesn't block
tick()
以给定的args
每interval
毫秒运行一次function
。 单个时钟的持续时间受root.after()
精度影响, root.after()
长远来看,稳定性仅取决于timer()
函数。
这是一个打印一些统计数据的脚本,每分钟240
次:
#!/usr/bin/env python from __future__ import division, print_function import sys from timeit import default_timer try: from Tkinter import Tk except ImportError: # Python 3 from tkinter import Tk def timer(): return int(default_timer() * 1000 + .5) def tick(interval, function, *args): root.after(interval - timer() % interval, tick, interval, function, *args) function(*args) # assume it doesn't block def bpm(milliseconds): """Beats per minute.""" return 60000 / milliseconds def print_tempo(last=[timer()], total=[0], count=[0]): now = timer() elapsed = now - last[0] total[0] += elapsed count[0] += 1 average = total[0] / count[0] print("{:.1f} BPM, average: {:.0f} BPM, now {}" .format(bpm(elapsed), bpm(average), now), end='\r', file=sys.stderr) last[0] = now interval = 250 # milliseconds root = Tk() root.withdraw() # don't show GUI root.after(interval - timer() % interval, tick, interval, print_tempo) root.mainloop()
速度仅在我的机器上接近一个节拍:240±1。