2

my program has a main window and in this window it runs a thread to read the power from a photodetector then it sends a signal, which is captured by a slot in the main window that updates the main window's gui.

Then I have another widget (let's call it programming widget) that pops from the main window, which is basically a plain text into which the user can insert commands so the program will execute them. Now the issue comes:

When I just open the programming widget with "show", the main window keeps updating the photodetector. However, when I start executing a sequence from the programming widget the main window freezes and the photodetector readings stops with it while is executing the commands (I guess it keeps reading because is in another thread but it stops updating the main window's gui).

My question is: The programming widget is already, by default, in another thread since the main window keeps working when it's popped. So, why it (main window)freezes when the programming widget is in a loop?

Here is the Thread code:

class PDThread(QtCore.QThread):

valueupdate = QtCore.pyqtSignal(float)
state = 1

def __init__(self, state, port):
    QtCore.QThread.__init__(self)
    self.photodetector = PM100D()
    self.port = port

def run(self):
        while True:
            try:
                self.photodetector.connect(self.port)
                break
            except:
                self.dialog = dialog_classes.DialogPort()
                self.dialog.set_instrument('PM100D')
                self.dialog.exec_()
                ret = self.dialog.pm100d.itemText(self.dialog.pm100d.currentIndex())
                if ret == QtGui.QDialog.Accepted:
                    self.port = str(ret)
                else:
                    self.photodetector.__del__()
                    self.quit()
                    return

        window.PDState = 1
        window.startpd.setText('Stop PD')     
        while self.state == 1:
            time.sleep(0.1)
            value = self.photodetector.get_pow()
            self.valueupdate.emit(value)

Here is the function that creates the thread and the slot in the main window:

def ActionConnect_PM100D(self):
    if self.PDState == 0:
        self.PD = PDThread(self.PDState, self.PDport)
        self.PD.valueupdate.connect(self.PDHandler)
        self.threads = []
        self.threads.append(self.PD)
        self.PD.start()
    else:
        self.PDState = 0
        self.PD.state = 0
        self.startpd.setText('Start PD')


def PDHandler(self, value):
    ref = float(self.refpd.value())
    self.outputpd.setText(str(value-ref))

Here also in the main window, the function that creates the widget:

    def ActionSetup(self):
    self.program = dialog_classes.Programming(self.newport, self.output, self.outputpd)
    self.program.show()

Finally, the widget code:

class Programming(QtGui.QDialog, Ui_Programming_Dialog, gui.MyApp):

def __init__(self, ESP300, output, outputpd):
    QtGui.QDialog.__init__(self)
    self.setupUi(self)
    self.setWindowTitle('Programming Setup')
    self.openbuttom.clicked.connect(self.Open)
    self.save.clicked.connect(self.Save)
    self.execute.clicked.connect(self.Execute)
    self.newport = ESP300
    self.output = output
    self.outputpd = outputpd

def Open(self):
    self.fileName = QtGui.QFileDialog.getOpenFileName(self, 'OpenFile', '', '*.txt')
    try:
        text = open(self.fileName).read()
    except:
        None
    else:
        self.program.setPlainText(text)

def Save(self):
    self.fileName = QtGui.QFileDialog.getSaveFileName(self, 'Save File', '', '*.txt')
    try:
        open(self.fileName, 'w').write(str(self.program.toPlainText()))
    except:
        None

def Execute(self):
    text = str(self.program.toPlainText())
    lines = text.split('\n')
    for line in lines:
        arg = []
        List = line.split(',')
        for word in List:
            arg.append(word.strip())

        if arg[0].lower() == 'move':
            if arg[1].lower() == 'x':
                self.newport.move_x(float(arg[2]))
            elif arg[1].lower() == 'y':
                self.newport.move_y(float(arg[2]))
            elif arg[1].lower() == 'z':
                self.newport.move_z(float(arg[2]))
        elif arg[0].lower() == 'wait':
            self.newport.wait()
        elif arg[0].lower() == 'home':
            self.newport.home()
            while True:
                try:
                    self.GetPosition()
                except:
                    time.sleep(0.5)
                else:
                    if self.newport.x == 0 and self.newport.y == 0 and self.newport.z == 0:
                        break
        elif arg[0].lower() == 'power on':
            self.newport.power_on(arg[1])
        elif arg[0].lower() == 'power off':
            self.newport.power_off(arg[1])
        elif arg[0].lower() == 'pos':
            self.newport.pos_x(arg[1])
            self.newport.pos_y(arg[2])
            self.newport.pos_z(arg[3])
            while True:
                try:
                    self.GetPosition()
                except:
                    time.sleep(0.5)
                else:
                    time.sleep(1)
                    break
        elif arg[0].lower() == 'get pos':
            self.GetPosition()
        elif arg[0].lower() == 'get error':
            self.GetError()
        elif arg[0].lower() == 'get pow':
            print(self.outputpd.toPlainText())

        time.sleep(2)


def closeIt(self):
    self.close()

Thank you for your support.

Eduardo
  • 631
  • 1
  • 12
  • 25
  • 1
    No `QWidget`-derived object can be in any thread other than the main thread. Your code isn't multithreaded, it simply blocks the UI when you're in a loop. UI code works by executing an event loop that dispatches events, when you're stuck in any slots or event handlers, event loop doesn't run. Invert the flow of control: instead of waiting for things in a loop, use a timer that calls your code every once in a while to read things, or react to new data as it becomes available iff you're using e.g. a `QSerialPort`. – Kuba hasn't forgotten Monica Sep 19 '16 at 22:10
  • You may wish to look at the Stack Overflow PyQt [documentation page on threads](http://stackoverflow.com/documentation/pyqt/2775/using-threads-with-pyqt#t=201609200232140019483) too. – three_pineapples Sep 20 '16 at 02:33

2 Answers2

1

Generally, the way this is done is to create a class derived from QObject that handles all the non-Qt data collection and move that into a separate thread using the worker model.

Then, you can use signals to pass data back and forth between the main (GUI) thread and the Worker thread, and to trigger events (like popping up a dialog in the main thread because of an event in the worker thread).

Community
  • 1
  • 1
Brendan Abel
  • 35,343
  • 14
  • 88
  • 118
  • I tried moving the Execute method to another thread using the worker model as you suggested but it stills freezes the gui. It doesn't make sense to me why it isn't working now. – Eduardo Sep 21 '16 at 11:43
1

I tried moving the Execute method to another thread using the worker model as suggested but it also froze the gui, I don't know why. Probably I did something wrong.

However, when I created another thread directly in the Class implemented to execute the loop and it worked. I followed this example: https://nikolak.com/pyqt-threading-tutorial/

In addition, here is my code. I hope it can help others as well, thank you.

self.worker_thread = []

def Execute(self):
    self.execution = ProgramExecution(self.newport, self.output, self.outputpd, self.program)
    self.worker_thread.append(self.execution)
    self.execution.start()

And here is the Thread Class:

class ProgramExecution(QtCore.QThread):

_signalStatus = QtCore.pyqtSignal(str)

def __init__(self, ESP300, output, outputpd, program):
    QtCore.QThread.__init__(self)
    self.newport = ESP300
    self.output = output
    self.outputpd = outputpd
    self.program = program

def __del__(self):
    self.wait()

def run(self):
    text = str(self.program.toPlainText())
    lines = text.split('\n')
    for line in lines:
        arg = []
        List = line.split(',')
        for word in List:
            arg.append(word.strip())

        if arg[0].lower() == 'move':
            if arg[1].lower() == 'x':
                self.newport.move_x(float(arg[2]))
            elif arg[1].lower() == 'y':
                self.newport.move_y(float(arg[2]))
            elif arg[1].lower() == 'z':
                self.newport.move_z(float(arg[2]))
        elif arg[0].lower() == 'wait':
            self.newport.wait()
        elif arg[0].lower() == 'home':
            self.newport.home()
            while True:
                try:
                    self.GetPosition()
                except:
                    time.sleep(0.5)
                else:
                    if self.newport.x == 0 and self.newport.y == 0 and self.newport.z == 0:
                        break
        elif arg[0].lower() == 'power on':
            self.newport.power_on(arg[1])
        elif arg[0].lower() == 'power off':
            self.newport.power_off(arg[1])
        elif arg[0].lower() == 'pos':
            self.newport.pos_x(arg[1])
            self.newport.pos_y(arg[2])
            self.newport.pos_z(arg[3])
            while True:
                try:
                    self.GetPosition()
                except:
                    time.sleep(0.5)
                else:
                    time.sleep(1)
                    break
        elif arg[0].lower() == 'get pos':
            self.GetPosition()
        elif arg[0].lower() == 'get error':
            self.GetError()
        elif arg[0].lower() == 'get pow':
            print(self.outputpd.toPlainText())

        time.sleep(2)
Eduardo
  • 631
  • 1
  • 12
  • 25