0

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?

wedesoft
  • 2,781
  • 28
  • 25
Ogi Moore
  • 131
  • 9
  • 1
    That is a general computing problem: normal exiting the thread is when the thread is cooperating. I once was writing an automated test framework and tried to find the way to terminate the thread without any conditions just with C++ 11: https://stackoverflow.com/questions/12207684/how-do-i-terminate-a-thread-in-c11 So, the guess is: no matter C++ or Python, you can only force it. All other ways involve the thread listening on some "exit" signal. And if the code is running NumPy then NumPy needs to react. – Alexander V Oct 06 '17 at 18:33
  • 1
    The thread can only quit after its `run` method returns. Any code executed inside `run` will block the thread. So you need some way to interrupt the code - e.g. set a flag the code can periodically check to see if it should continue. If the code is executing some kind of loop, this is easy. Otherwise, it is usually very hard or impossible to do. In which case, you must use a separate process, rather than a separate thread (i.e. use python's `multiprocessing` module). – ekhumoro Oct 06 '17 at 21:10
  • yeah I think use of the multiprocessing module to interrupt the numpy code is the way to go in my case; which of course begs the question why bother with a QThread at all at that point.... – Ogi Moore Oct 07 '17 at 20:30

0 Answers0