I think all GUI toolkits have the concept of a timer. In tk you us "after". In Qt you use QTimer. You could use multiple threads or processes, but if this is a GUI app, use what the GUI provides.
I would have only one timer and have it process a list of event_timer. An event timer is essentially a function to execute and a time to execute. The can run periodically, checking each event timer and executing the function when the event time is reached. Another choice is to set the timer to execute when the next event is scheduled. The logic is more complicated (you need to find the next event each time an event is added or executed and use that to set the timer wait), but it may result in better precision and less overhead.
You may want two types of event timers, periodic and one shot. A periodic event executes after some delay and continues to do so until stopped. A one shot executes after some delay and removes itself from the event timer list. Another possibility is to make everything a one shot and implement a periodic event by having the callback function add a new event timer.
This is an event timer I wrote for an application that uses PySide2 (Qt GUI).
"""Event is a scheduled function call
Events are added to a list that is processed during processor free time. The start
time is compared to the current time, and the event is run if the elapsed
time is greater than the event delay.
Running an event calls the event callback with the event arguments. These
are set at creation or by calling the "connect" or "execute" methods. The
event does not run again unless the "resume method is called
"execute" is a one-time event that is removed from the event list after it
is run. Use execute to do a delayed function call. Use "start" for
events that run periodically.
"""
import time
class Event:
"""Delayed function call"""
events = []
timer = None
@classmethod
def add_event(cls, event):
"""Add event to cycle"""
if not event in cls.events:
cls.events.append(event)
@classmethod
def remove_event(cls, event):
"""Remove event from cycle"""
cls.events.remove(event)
@classmethod
def timer_tic(cls):
"""Run periodically to schedule and fire events"""
current_time = time.time()
[event.tic(current_time) for event in cls.events]
def __init__(self):
"""Initialize Event."""
self.delay = 0.0
self.callback = None
self.args = ()
self.kwargs = {}
self.time = time.time()
self.oneshot = False
self.active = False
self._resume = False
def __repr__(self):
"""Return string representation of event"""
return f'(Event {self.callback}) delay {self.delay}, active {self.active}'
def tic(self, current_time):
"""Event countdown"""
if not self._resume:
self.time = current_time
elif current_time - self.time >= self.delay:
self._resume = False
if self.callback is not None:
self.callback(*self.args, **self.kwargs)
if self.oneshot:
self.stop()
def connect(self, callback, *args, **kwargs):
"""Function to execute when event fires"""
self.callback = callback
self.args = args
self.kwargs = kwargs
return self
def start(self):
"""Add event to the cycle"""
if not self.active:
self.oneshot = False
self.active = True
Event.add_event(self)
self.resume()
return self
def stop(self):
"""Remove event from the cycle"""
if self.active:
self.active = False
self.remove_event(self)
return self
def execute(self, callback, *args, **kwargs):
"""Use event as a delayed function call. Runs once and is removed from event loop.
Option args and kwargs must be list or tuple
"""
self.callback = callback
self.args = args
self.kwargs = kwargs
self.oneshot = True
self.active = True
self.resume()
Event.add_event(self)
def resume(self):
"""Run event again"""
self.time = time.time()
self._resume = True
return self
def setdelay(self, sec):
"""Set delay between events"""
self.delay = sec
return self
def dump(self):
"""Print list of events"""
for event in self.events:
print(event)
This is the code in the main program that creates the Event timer.
event_timer = QtCore.QTimer(None)
event_timer.timeout.connect(Event.timer_tic)
event_timer.start(1)