From e0c3ba9951507c25e39cddd28473ddfcf18baf42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Wed, 3 Sep 2025 09:37:20 -0400 Subject: [PATCH 1/3] Don't quit/restart thread --- qtapputils/managers/taskmanagers.py | 38 ++++++++++++++--------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/qtapputils/managers/taskmanagers.py b/qtapputils/managers/taskmanagers.py index 254e727..5138416 100644 --- a/qtapputils/managers/taskmanagers.py +++ b/qtapputils/managers/taskmanagers.py @@ -17,7 +17,7 @@ from time import sleep # ---- Third party imports -from qtpy.QtCore import QObject, QThread, Signal +from qtpy.QtCore import QObject, QThread, Signal, Qt # ---- Local imports from qtapputils.qthelpers import qtwait @@ -28,10 +28,12 @@ class WorkerBase(QObject): A worker to execute tasks without blocking the GUI. """ sig_task_completed = Signal(object, object) + sig_run_tasks = Signal() def __init__(self): super().__init__() self._tasks: OrderedDict[Any, tuple[str, tuple, dict]] = OrderedDict() + self.sig_run_tasks.connect(self.run_tasks, Qt.QueuedConnection) def _get_method(self, task: str): # Try direct, then fallback to underscore-prefixed (for backward @@ -67,7 +69,6 @@ def run_tasks(self): self.sig_task_completed.emit(task_uuid4, returned_values) self._tasks.clear() - self.thread().quit() class TaskManagerBase(QObject): @@ -102,9 +103,7 @@ def __init__(self, verbose: bool = False): @property def is_running(self): - return not (len(self._running_tasks) == 0 and - len(self._pending_tasks) == 0 and - not self._thread.isRunning()) + return len(self._running_tasks + self._pending_tasks) > 0 def run_tasks( self, callback: Callable = None, returned_values: tuple = None): @@ -134,13 +133,15 @@ def worker(self) -> WorkerBase: def set_worker(self, worker: WorkerBase): """"Install the provided worker on this manager""" - self._worker = worker self._thread = QThread() + + self._worker = worker self._worker.moveToThread(self._thread) - self._thread.started.connect(self._worker.run_tasks) + self._worker.sig_task_completed.connect( + self._handle_task_completed, Qt.QueuedConnection + ) - # Connect the worker signals to handlers. - self._worker.sig_task_completed.connect(self._handle_task_completed) + self._thread.start() # ---- Private API def _handle_task_completed( @@ -202,22 +203,19 @@ def _run_pending_tasks(self): print('Executing {} pending tasks...'.format( len(self._pending_tasks))) - # Even though the worker has executed all its tasks, - # we may still need to wait a little for it to stop properly. - i = 0 - while self._thread.isRunning(): - sleep(0.1) - i += 1 - if i > 100: - print("Error: unable to stop {}'s working thread.".format( - self.__class__.__name__)) - self._running_tasks = self._pending_tasks.copy() self._pending_tasks = [] for task_uuid4 in self._running_tasks: task, args, kargs = self._task_data[task_uuid4] self._worker.add_task(task_uuid4, task, *args, **kargs) - self._thread.start() + + self._worker.sig_run_tasks.emit() + + def close(self): + if hasattr(self, "_thread"): + qtwait(lambda: not self.is_running) + self._thread.quit() + self._thread.wait() class LIFOTaskManager(TaskManagerBase): From 1f7ed4c5524ece7f0d33f427b9aba614c0cec6a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Wed, 3 Sep 2025 11:46:55 -0400 Subject: [PATCH 2/3] Fix sig_run_tasks --- qtapputils/managers/taskmanagers.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/qtapputils/managers/taskmanagers.py b/qtapputils/managers/taskmanagers.py index 5138416..82b2d4f 100644 --- a/qtapputils/managers/taskmanagers.py +++ b/qtapputils/managers/taskmanagers.py @@ -28,12 +28,10 @@ class WorkerBase(QObject): A worker to execute tasks without blocking the GUI. """ sig_task_completed = Signal(object, object) - sig_run_tasks = Signal() def __init__(self): super().__init__() self._tasks: OrderedDict[Any, tuple[str, tuple, dict]] = OrderedDict() - self.sig_run_tasks.connect(self.run_tasks, Qt.QueuedConnection) def _get_method(self, task: str): # Try direct, then fallback to underscore-prefixed (for backward @@ -77,6 +75,7 @@ class TaskManagerBase(QObject): """ sig_run_tasks_started = Signal() sig_run_tasks_finished = Signal() + sig_run_tasks = Signal() def __init__(self, verbose: bool = False): super().__init__() @@ -138,8 +137,9 @@ def set_worker(self, worker: WorkerBase): self._worker = worker self._worker.moveToThread(self._thread) self._worker.sig_task_completed.connect( - self._handle_task_completed, Qt.QueuedConnection - ) + self._handle_task_completed, Qt.QueuedConnection) + self.sig_run_tasks.connect( + self._worker.run_tasks, Qt.QueuedConnection) self._thread.start() @@ -209,7 +209,7 @@ def _run_pending_tasks(self): task, args, kargs = self._task_data[task_uuid4] self._worker.add_task(task_uuid4, task, *args, **kargs) - self._worker.sig_run_tasks.emit() + self.sig_run_tasks.emit() def close(self): if hasattr(self, "_thread"): From 311e2df626ea10565dc6b685235d88f737dc7f25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Gosselin?= Date: Wed, 3 Sep 2025 11:57:40 -0400 Subject: [PATCH 3/3] Update test_taskmanagers.py --- qtapputils/managers/tests/test_taskmanagers.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/qtapputils/managers/tests/test_taskmanagers.py b/qtapputils/managers/tests/test_taskmanagers.py index f4b8911..85936b9 100644 --- a/qtapputils/managers/tests/test_taskmanagers.py +++ b/qtapputils/managers/tests/test_taskmanagers.py @@ -52,8 +52,9 @@ def task_manager(worker, qtbot): task_manager.set_worker(worker) yield task_manager - # We wait for the manager's thread to fully stop to avoid segfault error. - qtbot.waitUntil(lambda: not task_manager.is_running) + task_manager.close() + assert not task_manager.is_running + assert not task_manager._thread.isRunning() @pytest.fixture @@ -62,8 +63,9 @@ def lifo_task_manager(worker, qtbot): task_manager.set_worker(worker) yield task_manager - # We wait for the manager's thread to fully stop to avoid segfault error. - qtbot.waitUntil(lambda: not task_manager.is_running) + task_manager.close() + assert not task_manager.is_running + assert not task_manager._thread.isRunning() # =============================================================================