st-ten-1/src/lib/helpers/qthread_synchronizer.py
2024-10-24 13:34:14 +02:00

157 lines
6.0 KiB
Python

import sys
from functools import wraps
from queue import Queue
from types import FunctionType
from PyQt6.QtCore import QCoreApplication, QObject, QSemaphore, QThread, QTimer
def thread_command_execute(self):
while not self.__thread_command_queue.empty(): # only one consumer so no lock required
q = self.__thread_command_queue.get()
q["r"] = q["f"](*q["a"], **q["k"])
q["s"].release(1) # mark as consumed
self.__thread_command_timer.start()
def create_thread_command_executor(self):
self.__thread_command_id = int(QThread.currentThreadId())
self.__thread_command_queue = Queue()
self.__thread_command_timer = QTimer()
# self.__thread_command_timer.setTimerType(Qt.PreciseTimer)
self.__thread_command_timer.setInterval(10)
self.__thread_command_timer.setSingleShot(True)
self.__thread_command_timer.timeout.connect(self._thread_command_execute)
self.__thread_command_timer.start()
def run_or_enqueue(self, function, *args, **kwargs):
if self.__thread_command_id is None or int(QThread.currentThreadId()) == self.__thread_command_id:
return function(*args, **kwargs)
else:
s = {"f": function, "a": args, "k": kwargs, "s": QSemaphore(0), "r": None}
self.__thread_command_queue.put(s)
s["s"].acquire(max(s["s"].available(), 1)) # wait untill consumed
s["s"].release()
return s["r"]
def wrap(function, name):
if name == "__init__":
if function is not None:
@wraps(function)
def wrapper(self, *args, **kwargs):
self.__thread_command_id = None
return function(self, *args, **kwargs)
else:
def wrapper(self, *args, **kwargs):
self.__thread_command_id = None
return super(type(self), self).__init__(*args, **kwargs)
elif function is None:
return None
elif issubclass(type(function), FunctionType):
@wraps(function)
def wrapper(self, *args, **kwargs):
return run_or_enqueue(self, function, self, *args, **kwargs)
elif issubclass(type(function), classmethod):
@wraps(function)
def wrapper(self, *args, **kwargs):
return run_or_enqueue(self, function.__func__, self.__class__, *args, **kwargs)
elif issubclass(type(function), staticmethod):
@wraps(function)
def wrapper(self, *args, **kwargs):
return run_or_enqueue(self, function.__func__, *args, **kwargs)
elif issubclass(type(function), property):
wrapper = property(
fget=wrap(function.fget, function.fget.__name__) if function.fget is not None else None,
fset=wrap(function.fset, function.fset.__name__) if function.fset is not None else None,
fdel=wrap(function.fdel, function.fdel.__name__) if function.fdel is not None else None,
doc=function.__doc__,
)
else:
raise NotImplementedError(f"wrap for type {type(function)} not implemented")
return wrapper
class SynchronizingWrapper(type(QObject), type):
def __new__(meta, classname, bases, old__dict__):
new__dict__ = {}
for name, attr in old__dict__.items():
if name != "__init__":
if issubclass(type(attr), FunctionType) or issubclass(type(attr), classmethod) or issubclass(type(attr), staticmethod) or issubclass(type(attr), property):
attr = wrap(attr, name)
new__dict__[name] = attr
new__dict__["__init__"] = wrap(new__dict__.get("__init__", None), "__init__")
new__dict__["_create_thread_command_executor"] = create_thread_command_executor
new__dict__["_thread_command_execute"] = thread_command_execute
# return super(SynchronizingWrapper, meta).__new__(meta, classname, bases, new__dict__)
return type.__new__(meta, classname, bases, new__dict__)
class SynchronizingBase(QObject, metaclass=SynchronizingWrapper):
pass
if __name__ == "__main__":
class RObject(QObject):
def __init__(self, *args, **kwargs):
print("RObject.__init__", self, args, kwargs, "thread:", int(QThread.currentThreadId()))
super().__init__()
class Runner(RObject, metaclass=SynchronizingWrapper):
def __init__(self, *args, **kwargs):
print("Runner.__init__", self, args, kwargs, "thread:", int(QThread.currentThreadId()))
super().__init__(*args, **kwargs)
def start(self):
self._create_thread_command_executor()
print("Runner.start", self, "thread:", int(QThread.currentThreadId()))
self.timer = QTimer()
self.timer.setInterval(1000)
self.timer.timeout.connect(self.run)
self.timer.timeout.connect(self.run_classmethod)
self.timer.timeout.connect(self.run_staticmethod)
self.timer.timeout.connect(lambda: self.run_property)
self.timer.start()
def run(self):
print(f"run thread: {int(QThread.currentThreadId())}")
return True
@classmetho
def run_classmethod(cls):
print(f"run_classmethod thread: {int(QThread.currentThreadId())}")
return True
@staticmeth
def run_staticmethod():
print(f"run_staticmethod thread: {int(QThread.currentThreadId())}")
return True
@property
def run_property(self):
print(f"run_property thread: {int(QThread.currentThreadId())}")
return True
app = QCoreApplication(sys.argv)
runner = Runner()
# runner._create_thread_command_executor()
thread = QThread()
thread.setTerminationEnabled(True)
runner.moveToThread(thread)
thread.started.connect(runner.start)
thread.start()
timer = QTimer()
timer.setInterval(1000)
timer.timeout.connect(runner.run)
timer.timeout.connect(runner.run_classmethod)
timer.timeout.connect(runner.run_staticmethod)
timer.timeout.connect(lambda: runner.run_property)
timer.start()
sys.exit(app.exec_())