Skip to content

Commit

Permalink
Adding in the SafeThread class
Browse files Browse the repository at this point in the history
  • Loading branch information
simongarisch committed Oct 22, 2018
1 parent 48bfc2a commit fc155ad
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 8 deletions.
2 changes: 1 addition & 1 deletion safeqthreads/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
35 changes: 29 additions & 6 deletions safeqthreads/safeQThreads.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 '''

Expand All @@ -88,12 +108,15 @@ 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
app.processEvents()
end = time.time()
if (end - start) > max_wait_seconds:
return


2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
@@ -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"
],
Expand Down
22 changes: 22 additions & 0 deletions tests/test_safeqthreads.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]

0 comments on commit fc155ad

Please sign in to comment.