简体   繁体   English

如何在python中定期运行一个函数

[英]How to run a function periodically in python

I have a simple metronome running and for some reason when it is at a lower bpm it is fine, but at higher bpms it is inconsistent and isnt steady.我有一个简单的节拍器在运行,出于某种原因,当它在较低的 bpm 时很好,但在较高的 bpm 时它不一致且不稳定。 I don't know what is going on.我不知道发生了什么。 I want to try using something to run it periodically.我想尝试使用一些东西来定期运行它。 Is there a way to do that?有没有办法做到这一点?

Here is my code:这是我的代码:

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

Playing sound to emulate an ordinary metronome doesn't require "real-time" capabilities.播放声音以模拟普通节拍器不需要“实时”功能。

It looks like you use Tkinter framework to create the GUI.看起来您使用 Tkinter 框架来创建 GUI。 root.after() allows you to call a function with a delay . root.after()允许您使用延迟调用函数 You could use it to implement ticks:您可以使用它来实现刻度:

def tick(interval, function, *args):
    root.after(interval - timer() % interval, tick, interval, function, *args)
    function(*args) # assume it doesn't block

tick() runs function with given args every interval milliseconds. tick()interval毫秒使用给定的args运行function Duration of individual ticks is affected by root.after() precision but in the long run, the stability depends only on timer() function.单个滴答的持续时间受root.after()精度的影响, root.after()长远来看,稳定性仅取决于timer()函数。

Here's a script that prints some stats, 240 beats per minute:这是一个打印一些统计数据的脚本,每分钟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()

The tempo osculates only by one beat: 240±1 on my machine.节拍只有一个节拍:在我的机器上为 240±1。

Here's asyncio analog:这是asyncio模拟:

#!/usr/bin/env python3
"""Metronome in asyncio."""
import asyncio
import sys


async def async_main():
    """Entry point for the script."""
    timer = asyncio.get_event_loop().time
    last = timer()

    def print_tempo(now):
        nonlocal last
        elapsed = now - last
        print(f"{60/elapsed:03.1f} BPM", end="\r", file=sys.stderr)
        last = now

    interval = 0.250  # seconds
    while True:
        await asyncio.sleep(interval - timer() % interval)
        print_tempo(timer())


if __name__ == "__main__":
    asyncio.run(async_main())

See Talk: Łukasz Langa - AsyncIO + Music参见谈话:Łukasz Langa - AsyncIO + 音乐

Doing anything needing time precision is very difficult due to the need for the processor to share itself with other programs.由于处理器需要与其他程序共享自身,因此做任何需要时间精度的事情都非常困难。 Unfortunately for timing critical programs the operating system is free to switch to another process whenever it chooses.不幸的是,对于定时关键程序,操作系统可以随时选择切换到另一个进程。 This could mean that it may not return to your program until after a noticeable delay.这可能意味着它可能会在明显延迟之后才返回到您的程序。 Using time.sleep after import time is a more consistent way of trying to balance the time between beeps because the processor has less "reason" to switch away.导入时间后使用time.sleep是尝试平衡哔声之间时间的更一致的方法,因为处理器没有更少的“理由”切换。 Although sleep on Windows has a default granularity of 15.6ms, but I assume you will not need to play a beat in excess 64Hz.尽管 Windows 上的 sleep 的默认粒度为 15.6ms,但我假设您不需要播放超过 64Hz 的节拍。 Also it appears that you are using multithreading to try and address your issue, however, the python implementation of threading sometimes forces the threads to run sequentially.此外,您似乎正在使用多线程来尝试解决您的问题,但是,线程的 Python 实现有时会强制线程按顺序运行。 This makes matters even worse for switching away from your process.这使得离开您的流程变得更糟。

I feel that the best solution would be to generate sound data containing the metronome beep at the frequency desired.我觉得最好的解决方案生成包含所需频率的节拍器蜂鸣声的声音数据 Then you could play the sound data in a way the OS understands well.然后您可以以操作系统能够很好理解的方式播放声音数据。 Since the system knows how to handle sound in a reliable manner your metronome would then work.由于系统知道如何以可靠的方式处理声音,因此您的节拍器就可以工作了。

Sorry to disappoint but timing critical applications are VERY difficult unless you want to get your hands dirty with the system you are working with.很抱歉让您失望,但计时关键应用程序非常困难,除非您想亲自接触正在使用的系统。

I would like to tell you that you can't be precise with threads in case of timing because of race conditions (even when you are using locks and semaphores!).我想告诉你,由于竞争条件(即使你使用锁和信号量!),你不能精确地处理线程的时间。 Even I have faced the problem.甚至我也遇到过这个问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM