From fc155addc74e059c15d1551ee6cc14530e313f5b Mon Sep 17 00:00:00 2001 From: simongarisch Date: Mon, 22 Oct 2018 18:31:07 +1100 Subject: [PATCH] Adding in the SafeThread class --- safeqthreads/__init__.py | 2 +- safeqthreads/safeQThreads.py | 35 +++++++++++++++++++++++++++++------ setup.py | 2 +- tests/test_safeqthreads.py | 22 ++++++++++++++++++++++ 4 files changed, 53 insertions(+), 8 deletions(-) diff --git a/safeqthreads/__init__.py b/safeqthreads/__init__.py index ac5f927..288f33a 100644 --- a/safeqthreads/__init__.py +++ b/safeqthreads/__init__.py @@ -5,4 +5,4 @@ import time from weakref import WeakKeyDictionary, WeakSet from qtpy import QtWidgets, QtCore -from . safeQThreads import SafeQThread, SafeWorker, close_all_threads +from . safeQThreads import SafeQThread, SafeWorker, SafeThread, close_all_threads diff --git a/safeqthreads/safeQThreads.py b/safeqthreads/safeQThreads.py index b759326..c01dde0 100644 --- a/safeqthreads/safeQThreads.py +++ b/safeqthreads/safeQThreads.py @@ -2,18 +2,19 @@ defines the classes: -> SafeQThread (inherits from QtCore.QThread) and -> SafeWorker (inherits from QtCore.QObject) +-> SafeThread (inherits from threading.Thread) Objects for each of these classes will have a stop_running attribute (bool) that we can set to True if we want to indicate this thread should exit early. -a close_all_threads function is also provided that, when called, -will go through all existing SafeQThread objects and: --> set the object attribute stop_running = True --> call thread.quit() +a close_all_threads function is also provided that will: +-> for all SafeQThread objects set the object attribute stop_running to True and call thread.quit() +-> for all SafeThread objects set the object attribute stop_running to True -> wait for all SafeQThread objects to exit subject to a timeout of max_wait_seconds ''' import time +import threading from weakref import WeakSet from qtpy import QtWidgets, QtCore from . import errors @@ -80,6 +81,25 @@ def __init__(self, thread, parent=None): thread.register_worker(self) +class SafeThread(threading.Thread): + ''' we'll also create a standard library version which inherits from threading.Thread + this will also have a stop_running attribute signaling to the thread that it + should exit early. + ''' + stop_running = StopRunning() # descriptor for the stop_running attribute + thread_set = WeakSet() + + def __init__(self): + super(SafeThread, self).__init__() + self.daemon = True + self.thread_set.add(self) + + @classmethod + def stop_all_threads(cls): + for thread in cls.thread_set: + thread.stop_running = True + + def close_all_threads(max_wait_seconds=3): ''' wait for a certain number of seconds (max_wait_seconds) for the threads to finish ''' @@ -88,8 +108,9 @@ def close_all_threads(max_wait_seconds=3): start = time.time() app = QtWidgets.QApplication.instance() - SafeQThread.stop_all_threads() # set stop_running attribute to true for all SafeQThread instances - SafeQThread.quit_all_threads() # and call thread.quit for each of these threads + SafeThread.stop_all_threads() # set stop_running attribute to true for all SafeThread instances + SafeQThread.stop_all_threads() # do the same for all SafeQThread instances + SafeQThread.quit_all_threads() # and call thread.quit for QThreads while SafeQThread.any_threads_busy(): if app is not None: # http://pyqt.sourceforge.net/Docs/PyQt4/qcoreapplication.html#processEvents @@ -97,3 +118,5 @@ def close_all_threads(max_wait_seconds=3): end = time.time() if (end - start) > max_wait_seconds: return + + diff --git a/setup.py b/setup.py index 628c79d..7c7b05e 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,7 @@ from setuptools import setup, find_packages setup(name="safeqthreads", - version="0.0.1", + version="0.0.2", install_requires=[ "QtPy>=1.5.0" ], diff --git a/tests/test_safeqthreads.py b/tests/test_safeqthreads.py index 4d325dc..69eaee4 100644 --- a/tests/test_safeqthreads.py +++ b/tests/test_safeqthreads.py @@ -96,3 +96,25 @@ def loop(self): # now wait for this thread to finish properly safeqthreads.close_all_threads(max_wait_seconds=5) + + def test_safethread(self): + ''' this tests the SafeThread class (which inherits from threading.Thread) + not to be confused with the SafeQThread class (which inherits from QtCore.QThread) + ''' + class SomeThread(safeqthreads.SafeThread): + def __init__(self): + super(SomeThread, self).__init__() + self.start() + + def run(self): + while True: + if self.stop_running: + return + time.sleep(0.1) + + threads_list = [SomeThread() for _ in range(3)] + safeqthreads.close_all_threads() + time.sleep(1) # wait for 'if self.stop_running' to evaluate as True + # all of these threads in threads_list should now have their + # stop_running attribute set to True + assert False not in [thread.stop_running for thread in threads_list]