Search code examples
pythonmultithreadingpyqtkillqthread

Cancel background task (terminate QThread) in PyQt


This question is very much related to this one, which doesn't have a solution, but it is not exactly the same.

I would like to ask if there is a way of launching a background task in PyQt, and be able to kill it by pressing a button.

My problem is that I have an user interface and some external (3rd party) functions that take a while to compute. In order to not frozen the user interface while the task are computing, I run them on the background using QThread and synchronize the UI when they finish using signals.

However, I would like to add the option for the external user to press a button and cancel the current task (because the task is not needed/desired anymore).

Something that to me looks as simple as a kill -9 *task* in linux, is quite hard/ofuscated to obtain in Qt.

Right now I'm using custom Qthreads of the form of:

mythread = Mythread()
mythread.finished.connect(mycallback)
mythread.start()

Where Mythread inherits QThread overriding the run method.

In the user interface, there is one button that tries to kill that thread by either using:

mythread.exit(0)
mythread.quit()
mythread.terminate()

None of them works... I'm aware that the documentation states that the terminate method does have weird behaviours...

So the question is.. I'm facing this problem wrong? How to kill a QThread? If is not possible, is there any alternative to this?

Thanks!


Solution

  • It's a very common mistake to try to kill a QThread in the way you're suggesting. This seems to be due to a failure to realise that it's the long-running task that needs to be stopped, rather than the thread itself.

    The task was moved to the worker thread because it was blocking the main/GUI thread. But that situation doesn't really change once the task is moved. It will block the worker thread in exactly the same way that it was blocking the main thread. For the thread to finish, the task itself either has to complete normally, or be programmatically halted in some way. That is, something must be done to allow the thread's run() method to exit normally (which often entails breaking out of a blocking loop).

    A common way to cancel a long-running task is via a simple stop-flag:

    class Thread(QThread):
        def stop(self):
            self._flag = False
    
        def run(self):
            self._flag = True
            for item in get_items():
                process_item(item)
                if not self._flag:
                    break
            self._flag = False