@@ -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