1 year ago

#100437

test-img

Toakley

What is the best way to stop (interrupt) QRunnable in QThreadPool?

I have a long running task, which for example's sake I have made an infinite while loop:

def long_task(parent, progress_callback):
    top = 100000
    x = 0
    while True:
        if x < top:
            if not parent.stop:
                progress_callback.emit(x)
                x += 1
            else:
                break
        else:
            x = 0
            progress_callback.emit(x)
            x += 1

I have a Worker class that subclasses QRunnable, and then I can override the run() method with whatever function is passed to the Worker.

class ThreadWorker(QtCore.QRunnable):
    def __init__(self, fn, *args, **kwargs):
        super(ThreadWorker, self).__init__()
        self.fn = fn
        self.args = args
        self.kwargs = kwargs
        self.signals = ThreadWorkerSignals()
        self.kwargs['progress_callback'] = self.signals.progress
        self.running = False

    @QtCore.pyqtSlot()
    def run(self):
        self.running = True
        try:
            result = self.fn(*self.args, **self.kwargs)
        except:
            traceback.print_exc()
            exctype, value = sys.exc_info()[:2]
            self.signals.error.emit((exctype, value, traceback.format_exc()))
        else:
            self.signals.result.emit(result)  # Return the result of the processing
        finally:
            self.signals.finished.emit()  # Done

I create two instances of Worker within my MainWindow, and pass the same long-running task to each worker. Both workers are added to my MainWindow's QThreadPool and then I call start(worker) on each to begin the worker's run() method. I now have two threads running the infinite loop:

class MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()
        ## NOT SHOWING THE REST OF INIT CODE

    def create_workers(self):
        self.worker1 = ThreadWorker(self.long_task, parent=self)
        self.worker1.signals.progress.connect(lambda x: self.long_label_1.setText(str(x)))

        self.worker2 = ThreadWorker(self.long_task, parent=self)
        self.worker2.signals.progress.connect(lambda x: self.long_label_2.setText(str(x)))

        self.threadpool.start(self.worker1)
        self.threadpool.start(self.worker2)
        
        self.stop = False

Please note the self.stop attribute above - this also belongs to the MainWindow class.

All I want to do is break the loop (interrupt the run() method of a worker) when I press a button.

As you can see, I am referencing parent.stop during every iteration of the worker's while loop. Right now, if I press my button, MainWindow's stop attribute turns True and the loop breaks when the worker class sees this change.

    def stop_tasks(self):
        self.stop = True

This works fine and accomplishes my goal, but I am wondering if this is dangerous and if there is a better way to do this? I only ask because it seems risky to reference an outside class attribute from within a separate running thread, and I don't know what could go wrong.

multithreading

pyqt5

threadpool

qthread

qrunnable

0 Answers

Your Answer

Accepted video resources