I am making a GUI application that allows for some lengthy NumPy operations to be performed, and I want to give the user the ability to cancel an operation midway through.
The only way I can figure out a way to interrupt the numerical operations is to issue a QThread.terminate(); however per the Qt docs, its usage is very much discouraged.
Here is a functioning minimum code example.
from PyQt5 import QtWidgets, QtCore
import numpy as np
import sys
import time
from timeit import default_timer as timer
class ProcessorThread(QtCore.QThread):
finished = QtCore.pyqtSignal(object)
def __init__(self, callback):
super().__init__()
self.finished.connect(callback)
def run(self):
print('in run method')
matrix = np.linalg.inv(np.random.random(size=(10000, 10000)))
print('matrix inversion complete')
self.finished.emit(matrix)
self.quit()
class MainDialog(QtWidgets.QDialog):
def __init__(self):
super().__init__()
self.setWindowTitle('Example Threading')
self.buttonBox =\
QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Ok |
QtWidgets.QDialogButtonBox.Cancel)
self.buttonBox.accepted.connect(self.startThread)
self.buttonBox.rejected.connect(self.reject)
self.processingThread = None
layout = QtWidgets.QVBoxLayout()
layout.addWidget(self.buttonBox, alignment=QtCore.Qt.AlignHCenter)
self.setLayout(layout)
def startThread(self):
print('starting thread')
self.buttonBox.rejected.disconnect()
self.buttonBox.rejected.connect(self.abortThread)
self.processingThread = ProcessorThread(callback=self.threadFinished)
self.processingThread.start()
@QtCore.pyqtSlot(object)
def threadFinished(self, resulting_matrix):
print('thread finished')
@QtCore.pyqtSlot()
def abortThread(self):
start_time = timer()
print('received abort signal')
print('trying to quit')
self.processingThread.quit()
while self.processingThread.isRunning():
time.sleep(1)
print(f'thread is still running {int(timer() - start_time)} seconds after cancel')
print('thread has finally quit')
def main():
app = QtWidgets.QApplication(sys.argv)
window = MainDialog()
window.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
To demonstrate the issue, run this code, hit 'Ok' (to start the computation), and then hit 'Cancel', and you will notice despite hitting cancel, the numerical operations keeps progressing.
Here is the print output when I run it:
starting thread
in run method
received abort signal
trying to quit
thread is still running 1 seconds after cancel
thread is still running 2 seconds after cancel
thread is still running 3 seconds after cancel
thread is still running 4 seconds after cancel
thread is still running 5 seconds after cancel
thread is still running 6 seconds after cancel
thread is still running 7 seconds after cancel
thread is still running 8 seconds after cancel
thread is still running 9 seconds after cancel
thread is still running 10 seconds after cancel
thread is still running 11 seconds after cancel
thread is still running 12 seconds after cancel
thread is still running 13 seconds after cancel
thread is still running 14 seconds after cancel
thread is still running 15 seconds after cancel
thread is still running 16 seconds after cancel
thread is still running 17 seconds after cancel
thread is still running 18 seconds after cancel
matrix inversion complete
thread is still running 19 seconds after cancel
thread has finally quit
thread finished
The first idea I had was to run the numpy operations in a separate subprocess, and when I receive the quit signal, to send a signal.sigterm to that subprocess; but at this point I'm just guessing.
Any suggestions?