Skip to content

Commit 9b06478

Browse files
Merge pull request #41 from geo-stack/use_wait_not_qtwait
PR: More improvements to thread lifecycle management in TaskManagerBase
2 parents 7eb5c1e + 0528df5 commit 9b06478

File tree

1 file changed

+23
-16
lines changed

1 file changed

+23
-16
lines changed

qtapputils/managers/taskmanagers.py

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ def __init__(self, verbose: bool = False):
8181
self.verbose = verbose
8282

8383
self._worker = None
84+
self._thread_is_quitting = False
8485

8586
self._task_callbacks: dict[uuid.UUID, Callable] = {}
8687
self._task_data: dict[uuid.UUID, tuple[str, tuple, dict]] = {}
@@ -146,6 +147,7 @@ def set_worker(self, worker: WorkerBase):
146147

147148
self._worker.moveToThread(self._thread)
148149
self._thread.started.connect(self._worker.run_tasks)
150+
self._thread.finished.connect(self._handle_thread_finished)
149151

150152
self._worker.sig_task_completed.connect(self._handle_task_completed)
151153

@@ -170,16 +172,24 @@ def _handle_task_completed(
170172
# Remove references to the completed task from internal structures.
171173
self._cleanup_task(task_uuid4)
172174

173-
# If there are still running tasks, do not proceed further.
174-
if len(self._running_tasks) > 0:
175-
return
176-
177-
# We quit the thread here to ensure all resources are cleaned up
178-
# and to prevent issues with lingering events or stale object
179-
# references. This makes the worker lifecycle simpler and more robust,
180-
# especially in PyQt/PySide, and avoids subtle bugs that can arise
181-
# from reusing threads across multiple batches.
182-
self._thread.quit()
175+
# When all running task are completed, we quit the thread to ensure
176+
# all resources are cleaned up and to prevent issues with lingering
177+
# events or stale object references. This makes the worker lifecycle
178+
# more robust, especially in PyQt/PySide, and avoids subtle bugs that
179+
# can arise from reusing threads across multiple batches.
180+
if len(self._running_tasks) == 0:
181+
self._thread_is_quitting = True
182+
self._thread.quit()
183+
# NOTE: After 'quit()' is called, the thread's event loop exits
184+
# after processing pending events, and the 'QThread.finished'
185+
# signal is emitted. This triggers '_handle_thread_finished()',
186+
# which manages pending tasks or signals that all work is done.
187+
188+
def _handle_thread_finished(self):
189+
"""
190+
Handle when the thread event loop is shut down.
191+
"""
192+
self._thread_is_quitting = False
183193

184194
# If there are pending tasks, begin processing them.
185195
if len(self._pending_tasks) > 0:
@@ -226,15 +236,12 @@ def _run_pending_tasks(self):
226236
if len(self._pending_tasks) == 0:
227237
return
228238

239+
if self._thread_is_quitting:
240+
return
241+
229242
if self.verbose:
230243
print(f'Executing {len(self._pending_tasks)} pending tasks...')
231244

232-
# Ensure the thread is not running before starting new tasks.
233-
# This prevents starting a thread that is already active, which can
234-
# cause errors.
235-
if self._thread.isRunning():
236-
qtwait(lambda: not self._thread.isRunning())
237-
238245
# Move all pending tasks to the running tasks queue.
239246
self._running_tasks = self._pending_tasks.copy()
240247
self._pending_tasks = []

0 commit comments

Comments
 (0)