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)