Pause worker thread and wait for event from main thread
Asked Answered
Y

2

5

We have an application that executes different queries. It starts up to four threads, and runs the extractions on them.

That part looks like this:

    if len(self.threads) == 4:
        self.__maxThreadsMsg(base)
        return False
    else:
        self.threads.append(Extractor(self.ui, base))
        self.threads[-1].start()
        self.__extractionMsg(base)
        return True

Our Extractor class inherits QThread:

class Extractor(QThread):
    def init(self, ui, base):
        QThread.__init__(self)
        self.ui = ui
        self.base = base

    def run(self):
        self.run_base(base)

and self.ui is set to Ui_MainWindow():

class Cont(QMainWindow):
    def __init__(self, parent=None):
        QWidget.__init__(self,parent)
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

There is a specific base that sends data to the user (back to the main window) before proceeding (in this case, a pop-up with two buttons):

#This code is in the main file inside a method, not in the Extractor class
msg_box = QMessagebox()
msg_box.setText('Quantity in base: '.format(n))
msg_box.setInformativeText('Would you like to continue?')
msg_box.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel)
signal = msg_box.exec_()

How can I pause the thread at a certain point, display the window (which I believe would be returning to the main thread) and return to the worker thread, passing the button clicked event?

I read a bit about signals but it seems confusing as it is my first time dealing with threads.

Edit: After reading this question: Similar question, I altered the code to this:

On a method inside of the Cont class

thread = QThread(self)
worker = Worker()

worker.moveToThread(thread)
worker.bv.connect(self.bv_test)

thread.started.connect(worker.process()) # This, unlike in the linked question.. 
#doesn't work if I remove the parentheses of the process function. 
#If I remove it, nothing happens and I get QThread: "Destroyed while thread is still running"

thread.start()

@pyqtSlot(int)
def bv_test(self, n):
    k = QMessageBox()
    k.setText('Quantity: {}'.format(n))
    k.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
    ret = k.exec_()
    return ret

and this is the Worker class:

class Worker(QObject):

    #Signals
    bv = pyqtSignal(int)

    def process(self):
        self.bv.emit(99)

Now I just need to figure out how to send the ret value back to the worker thread so it starts the second process. I also keep getting this error:

TypeError: connect() slot argument should be a callable or a signal, not 'NoneType'

Yacketyyak answered 6/2, 2018 at 12:29 Comment(1)
Possible duplicate of PyQt4 Wait in thread for user input from GUIPalomo
B
6

Below is a simple demo based on the code in your question which does what you want. There is not much to say about it, really, other than that you need to communicate between the worker and the main thread via signals (in both directions). The finished signal is used to quit the thread, which will stop the warning message QThread: "Destroyed while thread is still running" being shown.

The reason why you are seeing the error:

TypeError: connect() slot argument should be a callable or a signal, not `NoneType'

is because you are trying to connect a signal with the return value of a function (which is None), rather than the function object itself. You must always pass a python callable object to the connect method - anything else will raise a TypeError.

Please run the script below and confirm that it works as expected. Hopefully it should be easy to see how to adapt it to work with your real code.

from PyQt4.QtCore import *
from PyQt4.QtGui import *

class Cont(QWidget):
    confirmed = pyqtSignal()

    def __init__(self):
        super(Cont, self).__init__()
        self.thread = QThread()
        self.worker = Worker()
        self.worker.moveToThread(self.thread)
        self.worker.bv.connect(self.bv_test)
        self.worker.finished.connect(self.thread.quit)
        self.confirmed.connect(self.worker.process_two)
        self.thread.started.connect(self.worker.process_one)
        self.thread.start()

    def bv_test(self, n):
        k = QMessageBox(self)
        k.setAttribute(Qt.WA_DeleteOnClose)
        k.setText('Quantity: {}'.format(n))
        k.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
        if k.exec_() == QMessageBox.Yes:
            self.confirmed.emit()
        else:
            self.thread.quit()

class Worker(QObject):
    bv = pyqtSignal(int)
    finished = pyqtSignal()

    def process_two(self):
        print('process: two: started')
        QThread.sleep(1)
        print('process: two: finished')
        self.finished.emit()

    def process_one(self):
        print('process: one: started')
        QThread.sleep(1)
        self.bv.emit(99)
        print('process: one: finished')

app = QApplication([''])
win = Cont()
win.setGeometry(100, 100, 100, 100)
win.show()
app.exec_()
Barbarossa answered 17/2, 2018 at 1:40 Comment(0)
C
2

If you want the thread to wait for the action, connect to a signal from the thread using

PyQt4.QtCore.Qt.BlockingQueuedConnection

as flag.

Now I do not understand why you need threading if you let them wait, which brings in a lot of complexity. For me the better solution would be to cut the task you want to perform in the threads in smaller pieces. Each time a piece is ready, you can ask if the user wants the next too.

Confutation answered 6/2, 2018 at 12:48 Comment(1)
There is only one specific extraction that needs a confirmation, when the program was coded, we didn't have that requirement.Yacketyyak

© 2022 - 2024 — McMap. All rights reserved.