1

I'm trying (and researching) with little success to emit a signal from a working Qthread to the main window. I don't seem to understand how I should go about this in the new syntax.

Here's a simple example.

from PySide.QtCore import *
from PySide.QtGui import *
import sys
import time

class Dialog(QDialog):

    def __init__(self, parent=None):
        super(Dialog, self).__init__(parent)

        button = QPushButton("Test me!")

        layout = QVBoxLayout()
        layout.addWidget(button)
        self.setLayout(layout)

        #self.button.clicked.connect(self.test) ----> 'Dialog' object has no attribute 'button'
        self.connect(button, SIGNAL('clicked()'), self.test)
        self.workerThread = WorkerThread()


    def test(self):
        self.workerThread.start()
        QMessageBox.information(self, 'Done!', 'Done.')


class WorkerThread(QThread):

    def __init__(self, parent=None):
        super(WorkerThread, self).__init__(parent)

    def run(self):
        time.sleep(5)
        print "Thread done!"


app = QApplication(sys.argv)
dialog = Dialog()
dialog.show()
app.exec_()

I understand that if I didn't have another thread I'd create the signal inside the Dialog class and connect it in the __init__ but how can I create a custom signal that can be emitted from WorkerThread and be used test()?

As a side question. You can see it commented out of the code that the new syntax for connecting the signal errors out. Is it something in my configurations?

I'm on OsX El Capitan, Python 2.7

Any help is highly appreciated! Thanks a lot

TL:DR: I'd like to emmit a signal from the WorkerThread after 5 seconds so that the test function displays the QMessageBox only after WorkingThread is done using the new syntax.

Brendan Abel
  • 35,343
  • 14
  • 88
  • 118

1 Answers1

2

Ok, it's been a long day trying to figure this out. My main resource was this: http://www.matteomattei.com/pyside-signals-and-slots-with-qthread-example/

In the new syntax, in order to handle signals from different threads, you have to create a class for your signal like so:

class WorkerThreadSignal(QObject):
    workerThreadDone = Signal()

This is how the WorkerThread end up looking like:

class WorkerThread(QThread):

    def __init__(self, parent=None):
        super(WorkerThread, self).__init__(parent)
        self.workerThreadSignal = WorkerThreadSignal()

    def run(self):
        time.sleep(3)
        self.workerThreadSignal.workerThreadDone.emit()

And for the connections on the Dialog class:

    self.workerThread = WorkerThread()
    self.buttonn.clicked.connect(self.test)

and:

    self.workerThreadSignal = WorkerThreadSignal()
    self.workerThread.workerThreadSignal.workerThreadDone.connect(self.success)

def success(self):
    QMessageBox.warning(self, 'Warning!', 'Thread executed to completion!')

So the success method is called once the signal is emitted.

What took me the longest to figure out was this last line of code. I originally thought I could connect directly to the WorkerThreadSignal class but, at least in this case, it only worked once I backtracked it's location. From the Dialog init to WorkerThread init back to the WorkerThreadSignal. I took this hint from the website mentioned above.

I find strange that I have to create the same local variables on both classes, maybe there's a way to create one global variable I can refer to instead all the current solution but it works for now.

I hope this helps someone also stuck in this process!

PS: The syntax problem for the connection was also solved. So everything is written with the new syntax, which is great.

  • 1
    This answer probably deserves it's own question as to why what you were trying to do didn't work, but I'll give it ago in the 500 characters I have! Signals are unique to the instance of the object, not the class (even though they are defined as a class variable). So you made two different signals, emitting one, but connecting to the other. Obviously this didn't work! What should also work is just ditching the `WorkerThreadSignal` class and moving the signal into `WorkerThread`, which makes it simpler! – three_pineapples May 18 '16 at 07:01
  • 1
    However, you may want to check out a different approach to threading (the worker model) which does not overwrite `QThread.run`. See [this](http://stackoverflow.com/q/35527439/1994235) and [this](http://stackoverflow.com/q/37173560/1994235) and [this](http://stackoverflow.com/q/37195489/1994235) question (and also related links there-in) – three_pineapples May 18 '16 at 07:04
  • Hey @three_pineapples, thanks a lot for the links and the explanation. After reading through what you said and the other resources I'm starting to grasp the idea of using the `moveToThread()` concept. I'm writing my first piece of software (after following a bunch of tutorials) so sometimes I get confused. As a side note, moving my Signal inside the `workerThread` returns that the object has no attribute "Signal", I'm guessing it's some instancing problem? – Abel Vargas May 18 '16 at 18:33
  • This is what it returns if I move the signal into `WorkerThread`: `AttributeError: 'PySide.QtCore.Signal' object has no attribute 'connect'` – Abel Vargas May 18 '16 at 18:39
  • I just implemented the `moveToThread()` approach and it works great. My mind is being stretched a little bit but it seems like the code is clearer this way, where you implement the thread inside the function that needs it. Thanks a lot @three_pineapples , your reply was a mind opener! – Abel Vargas May 18 '16 at 19:03
  • Using the `moveToThread()` method is giving me `QThread: Destroyed while thread is still running` when I close the UI, how do you approach closing a thread after it's done? Should I use the same signal to execute a `QThread.quit()`? – Abel Vargas May 18 '16 at 23:07
  • Not sure why you got the attribute error. Possibly you defined the signal in the wrong place? They are not normal attributes and must be defined in class, not a method of the class. If the message about a destroyed `QThread` is only happening when you quit, I wouldn't worry about it. It is just to do with the way objects are garbage collected I think. But ensuring the `QThread.quit` slot is called might solve it (or may not if it the thread is garbage collected before the slot is executed) – three_pineapples May 19 '16 at 00:34
  • Yeah, it's just when I quit the application. I'll see if `QThread.quit` solves it, otherwise I'll leave it like this. Thanks a lot! – Abel Vargas May 19 '16 at 00:39
  • When you close the application, or in the `closeEvent` you could check to see if the thread `isRunning()` or `isFinished()` – Tom Myddeltyn May 19 '16 at 14:36