How to run a function periodically in python How to run a function periodically in python tkinter tkinter

How to run a function periodically in python


Playing sound to emulate an ordinary metronome doesn't require "real-time" capabilities.

It looks like you use Tkinter framework to create the GUI. root.after() allows you to call a function with a delay. 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. Duration of individual ticks is affected by root.after() precision but in the long run, the stability depends only on timer() function.

Here's a script that prints some stats, 240 beats per minute:

#!/usr/bin/env pythonfrom __future__ import division, print_functionimport sysfrom timeit import default_timertry:    from Tkinter import Tkexcept ImportError: # Python 3    from tkinter import Tkdef 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 blockdef bpm(milliseconds):    """Beats per minute."""    return 60000 / millisecondsdef 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] = nowinterval = 250 # millisecondsroot = Tk()root.withdraw() # don't show GUIroot.after(interval - timer() % interval, tick, interval, print_tempo)root.mainloop()

The tempo osculates only by one beat: 240±1 on my machine.

Here's asyncio analog:

#!/usr/bin/env python3"""Metronome in asyncio."""import asyncioimport sysasync 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


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. 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. 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. 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.