views:

73

answers:

1

Using Python 2.5 and PyQt 4.4.3, I couldn't find any question this specific in Python, so sorry if I'm repeating the other Qt referenced questions below, but I couldn't easily understand that C code.

I've got two classes, a GUI and a thread, and I'm trying to get return values from the thread. I've used the link in here as base to write my code, which is working just fine. To sum it up and illustrate the question in code here (I don't think this code will run on itself):

#!/usr/bin/python2.5

# this is a testing module, and almost everything in here is just there to make the script work
# the relevant issues for the testing are noted in comments

from PyQt4 import QtCore, QtGui
import sys, time

class MainWindow (QtGui.QWidget):
    def __init__(self, parent=None):
        QtGui.QWidget.__init__(self, parent)

        self.buttonDaemon = QtGui.QPushButton(self)
        self.layout = QtGui.QVBoxLayout(self)
        self.layout.addWidget(self.buttonDaemon)
        self.setLayout(self.layout)

        self.thread = Worker()
        self.connect(self.thread, QtCore.SIGNAL('finished()'), self.unfreezeUi)
        self.connect(self.thread, QtCore.SIGNAL('terminated()'), self.unfreezeUi)
        #self.thread.stop.connect(self.stopped) # part of proposed solution
        self.connect(self.thread, QtCore.SIGNAL('stopped(int)'), self.stopped) #adapted from proposed solution

        self.connect(self.buttonDaemon, QtCore.SIGNAL('clicked()'), self.pressDaemon)

    def unfreezeUi (self):
        self.buttonDaemon.setEnabled(True)

    # the problem begins below: I'm not using signals, or queue, or whatever, while I believe I should for StopSignal and DaemonRunning
    def pressDaemon (self):
        self.buttonDaemon.setEnabled(False)
        if self.thread.isDaemonRunning():
            self.thread.setDaemonStopSignal(True)
            self.buttonDaemon.setText('Daemon - run code every %s sec'% 1)
        else:
            self.thread.startDaemon()
            self.buttonDaemon.setText('Stop Daemon')
            self.buttonDaemon.setEnabled(True)

    # part of proposed solution
    def stopped (self, val):
        print 'stopped ' + str(val)

class Worker (QtCore.QThread):
    daemonIsRunning = False
    daemonStopSignal = False
    daemonCurrentDelay = 0

    def isDaemonRunning (self): return self.daemonIsRunning
    def setDaemonStopSignal (self, bool): self.daemonStopSignal = bool

    def __init__ (self, parent = None):
        QtCore.QThread.__init__(self, parent)
        self.exiting = False
        self.thread_to_run = None

    def __del__ (self):
        self.exiting = True
        self.thread_to_run = None
        self.wait()

    def run (self):
        if self.thread_to_run != None:
            self.thread_to_run(mode='continue')

    #stop = QtCore.pyqtSignal(int) # part of proposed solution

    def startDaemon (self, mode = 'run'):
        if mode == 'run':
            self.thread_to_run = self.startDaemon # I'd love to be able to just pass this as an argument on start() below
            return self.start() # this will begin the thread

        # this is where the thread actually begins
        self.daemonIsRunning = True
        print 'Daemon started'
        self.daemonStopSignal = False
        sleepStep = 0.1 # don't know how to interrupt while sleeping - so the less sleepStep, the faster StopSignal will work

        # begins the daemon in an "infinite" loop
        while self.daemonStopSignal == False and not self.exiting:
            print 'Daemon running'
            # here, do any kind of daemon service

            delay = 0
            while self.daemonStopSignal == False and not self.exiting and delay < 1:
                print 'Daemon sleeping'
                time.sleep(sleepStep) # delay is actually set by while, but this holds for 'sleepStep' seconds
                delay += sleepStep

        # daemon stopped, reseting everything
        self.daemonIsRunning = False
        print 'Daemon stopped'
        #self.stop.emit(self.daemonIsRunning) # part of proposed solution
        self.emit(QtCore.SIGNAL('stopped(int)'), self.daemonIsRunning) # adapted from proposed solution
        self.emit(QtCore.SIGNAL('terminated'))

def main (args):
    app = QtGui.QApplication(args)
    win = MainWindow()
    win.show()
    sys.exit(app.exec_())

if __name__ == "__main__":
    main(sys.argv)

Tho it's quite big, I hope this is pretty clear. The main point is on def pressDaemon. Specifically all 3 self.thread calls. The last one, self.thread.startDaemon() is just fine, and exactly as the example. I doubt that represents any issue.

The problem is being able to set the Daemon Stop Signal and retrieve the value if it's running. I'm not sure that it's possible to set a stop signal on QtCore.QtThread, because I've tried doing the same way and it didn't work. But I'm pretty sure it's not possible to retrieve a return result from the emit.

So, there it is. I'm using direct calls to the thread class, and I'm almost positive that's not a good design and will probably fail when running under stress. I read about that queue, but I'm not sure it's the proper solution here, or if I should be using Qt at all, since this is Python. And just maybe there's nothing wrong with the way I'm doing.

edit: After the proposed solution, I've used it to make the code runnable. Thanks again Max, for that! And I also left there comments about the proposal to let it clear how unfortunate that it doesn't help solving the question.

+2  A: 

You can declare the signal, if you want to send a value from one thread to another.

Here is your example, modified a little, for it can be executed as a sole script. 'Stop' signal fires when you hit the button that stops the daemon and slot 'stopped' of 'MainWindow' executes and print the value, that have been succesfully transferred from one thread to another.

There are some types of signals in Qt: Queued, Blocking f.x. (RTM, it is perfectly clear) and you can declare them strictly. But default signal declaration with omitted parameters recognizes automatically, if connected entity (slot or another signal) is in another thread and works properly, so just use simple declaration way.

For more information on signals and slots look at the documentation on PyQt from riverbankcomputing and maybe my another answers about this.

from sys import argv, exit
from PyQt4 import QtCore, QtGui, uic
import PyQt4
import time


class MainWindow (QtGui.QWidget):
    # this is just a reference and not really relevant to the question
    def __init__ (self, args):
        self.app = MainApp(args)
        QtGui.QWidget.__init__(self)
        self.buttonDaemon = QtGui.QPushButton(self)
        self.buttonConvert = QtGui.QPushButton(self)
        self.layout = QtGui.QVBoxLayout(self)
        self.layout.addWidget(self.buttonDaemon)
        self.layout.addWidget(self.buttonConvert)
        self.setLayout(self.layout)

        self.thread = Worker() # this does not begin a thread - look at "Worker.run" for mor details
        self.connect(self.thread, QtCore.SIGNAL('finished()'), self.unfreezeUi)
        self.connect(self.thread, QtCore.SIGNAL('terminated()'), self.unfreezeUi)
        self.thread.stop.connect(self.stopped)

        self.connect(self.buttonDaemon, QtCore.SIGNAL('clicked()'), self.pressDaemon)

    # the problem begins below: I'm not using signals, or queue, or whatever, while I believe I should
    def pressDaemon (self):
        self.buttonDaemon.setEnabled(False)
        if self.thread.isDaemonRunning():
            self.thread.setDaemonStopSignal(True)
            self.buttonDaemon.setText('Daemon - converts every %s sec'% 1)
        else:
            self.buttonConvert.setEnabled(False)
            self.thread.startDaemon()
            self.buttonDaemon.setText('Stop Daemon')
            self.buttonDaemon.setEnabled(True)

    def unfreezeUi(self):
        print '!!'

    def stopped(self, val):
        print 'stopped ' + str(val)

# this whole class is just another reference
class Worker (QtCore.QThread):
    daemonIsRunning = False
    daemonStopSignal = False
    daemonCurrentDelay = 0

    def isDaemonRunning (self): return self.daemonIsRunning
    def setDaemonStopSignal (self, bool): self.daemonStopSignal = bool

    def __init__ (self, parent = None):
        QtCore.QThread.__init__(self, parent)
        self.exiting = False
        self.thread_to_run = None  # which def will be running

    def __del__ (self):
        self.exiting = True
        self.thread_to_run = None
        self.wait()

    def run (self):
        if self.thread_to_run != None:
            self.thread_to_run(mode='continue')

    stop = QtCore.pyqtSignal(int)

    def startDaemon (self, mode = 'run'):
        if mode == 'run':
            self.thread_to_run = self.startDaemon # I'd love to be able to just pass this as an argument on start() below
            return self.start() # this will begin the thread

        # this is where the thread actually begins
        self.daemonIsRunning = True
        self.daemonStopSignal = False
        sleepStep = 0.1 # don't know how to interrupt while sleeping - so the less sleepStep, the faster StopSignal will work

        # begins the daemon in an "infinite" loop
        while self.daemonStopSignal == False and not self.exiting:
            # here, do any kind of daemon service

            delay = 0
            while self.daemonStopSignal == False and not self.exiting and delay < 1:
                time.sleep(sleepStep) # delay is actually set by while, but this holds for N second
                delay += sleepStep

        # daemon stopped, reseting everything
        self.daemonIsRunning = False
        self.stop.emit(self.daemonIsRunning)
        self.emit(QtCore.SIGNAL('terminated'))

class MainApp(QtGui.QApplication):
    def __init__(self, args):
        QtGui.QApplication.__init__(self, args)

if __name__ == "__main__":
    main = MainWindow(argv)
    main.show()
    exit(main.app.exec_())
Max
I really appreciate all the time you took to review the code and even write on top of it! :) but I couldn't make any use of it. I actually first thought this was the solution, until being able to try it! :( As far as I can tell, you just added a signal using the new style ( http://tinyurl.com/ycdrypv ), which doesn't work for me. I needed to **send a signal from MainWindow to Thread**, not the other way, which I already can do. But I'm also not even sure I should be trying to send that signal, or use some other technique. Again, thanks for your time, and be sure I'll be reading all that! :)
Cawas
Oh, sorry. I have misunderstood you.Have you seen this topic? http://stackoverflow.com/questions/638251/how-to-emit-cross-thread-signal-in-qt
Max
@Max well, haven't seen it before, but it still doesn't help me much. It's not only C, but it's again sending signals just from one place. Tho it did gave me an idea of a new 'syntax guessing'... I'll try it and let you know. P.S. in SO / SE sites I only get notified of answers to my questions, comments on my posts (answers or questions) and mentions on posts I've commented on. In this case, I'd only read your "answer" if you notified me by mentioning me or, like I did, by accident.
Cawas
I can't believe I haven't voted this answer up before, even if it's not the solution! :P
Cawas