Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pause worker thread and wait for event from main thread

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'

like image 897
Onilol Avatar asked Feb 06 '18 12:02

Onilol


People also ask

How do I pause main thread?

Thread. sleep() method can be used to pause the execution of current thread for specified time in milliseconds.

Does thread sleep block other threads?

Sleep method causes the current thread to immediately block for the number of milliseconds or the time interval you pass to the method, and yields the remainder of its time slice to another thread. Once that interval elapses, the sleeping thread resumes execution. One thread cannot call Thread. Sleep on another thread.

How to wait for an event in Python?

Using event.wait() When we want a thread to wait for an event, we can use the wait() method on the event object whose internal flag is set to false, which will block the thread until the set() method sets the internal flag of that event object to true. The thread is not blocked if the internal flag is true on entry.


2 Answers

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_()
like image 177
ekhumoro Avatar answered Nov 03 '22 03:11

ekhumoro


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.

like image 42
Johan van Breda Avatar answered Nov 03 '22 03:11

Johan van Breda