簡體   English   中英

如何在python中定期運行一個函數

[英]How to run a function periodically in 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

播放聲音以模擬普通節拍器不需要“實時”功能。

看起來您使用 Tkinter 框架來創建 GUI。 root.after()允許您使用延遲調用函數 您可以使用它來實現刻度:

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

tick()interval毫秒使用給定的args運行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。

這是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())

參見談話:Łukasz Langa - AsyncIO + 音樂

由於處理器需要與其他程序共享自身,因此做任何需要時間精度的事情都非常困難。 不幸的是,對於定時關鍵程序,操作系統可以隨時選擇切換到另一個進程。 這可能意味着它可能會在明顯延遲之后才返回到您的程序。 導入時間后使用time.sleep是嘗試平衡嗶聲之間時間的更一致的方法,因為處理器沒有更少的“理由”切換。 盡管 Windows 上的 sleep 的默認粒度為 15.6ms,但我假設您不需要播放超過 64Hz 的節拍。 此外,您似乎正在使用多線程來嘗試解決您的問題,但是,線程的 Python 實現有時會強制線程按順序運行。 這使得離開您的流程變得更糟。

我覺得最好的解決方案生成包含所需頻率的節拍器蜂鳴聲的聲音數據 然后您可以以操作系統能夠很好理解的方式播放聲音數據。 由於系統知道如何以可靠的方式處理聲音,因此您的節拍器就可以工作了。

很抱歉讓您失望,但計時關鍵應用程序非常困難,除非您想親自接觸正在使用的系統。

我想告訴你,由於競爭條件(即使你使用鎖和信號量!),你不能精確地處理線程的時間。 甚至我也遇到過這個問題。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM