157 lines
6.0 KiB
Python
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_())
|