0

so i have a GUI that uses lots of pexpects to login to a program and retrieve information. So i started integrating that code into a Qthread; everything was working fine, until i remembered to test the error messages, it appears that the emited signal inside the Qthread is sometimes not beeing caught by my main code.

Here is the piece of code im talking about:

(python 3, pyqt5)

Code revision 1 - changelog: testf - All values are now loaded into the Qthread using signals and slots - untested, will update soon- Qthread - added "signal receiving function" (tt signal added to loginb)

def testf(self):
    self.movie.start()
    self.gif.show()
    self.myThread = thread()
    self.loginb.tt.connect(self.myThread.valueset)
    self.loginb.tt.emit(self.userfield.text(), self.pwfield.text(), self.checkBox.text(), self.checkBox.isChecked())
    self.myThread.start()
    self.myThread.done.connect(self.threadrelay)

def threadrelay(self, code, text): #decides what to do given the thread's emited signal arguments
    print('enter relay',code)
    self.movie.stop()
    self.gif.hide()
    self.myThread.exit()
    if code == 3:
        print(text)
        print('trying to open main diag')
        self.Omaindialog(text)
    elif code == 4:
        self.wrongpass.setText(text)
        self.wrongpass.show()            
    else:
        self.errordiag(text,code)

thread code:

class thread(QtCore.QThread):
    done=pyqtSignal(int, str)

    def valueset(self,u,p,c,ci):
        print('received values')
        self.user=u
        self.paw=p
        self.check=c
        self.checkis=ci

    def run (self):
        print(self.check == "Save password", self.check)
        if (self.paw=='' or self.user=='') and self.check == "Save password":
            print('if 1')
            txt=("loads of html code here")
            self.done.emit(4,txt)
            print('emited')
        elif ('@' not in self.user or '.' not in self.user) and self.check == "Save password":
            print('if 2')
            txt=("loads of html code here")
            self.done.emit(4,txt)
            print('emited')
        else:

this shows just the portion affected (maybe more of my code is affected by this, idk yet, i dont really unerstand what is happening here) - so the "testf" function is called (pushbutton), it starts the qthread, then the qthread emits the done signal with parameters that will be evaluated by the "threadrelay" function.

Here is some output (about 5-10 secs bettween each pushbutton click):

True Save password
if 2
emited
True Save password
if 2
emited
True Save password
if 2
emited
True Save password
if 2
emited
True enter relaySave password <- only time it entered the "threadrelay" function)
4if 2

emited
True Save password
if 2
emited
True Save password
if 2
emited
True Save password
if 2
emited
True Save password
if 2
emited

EDIT1: lol i went out for a cup of cofee came back, and now it works more often than it doesn't... I'm baffled... it still doesn't work sometimes though

EDIT2: I realize i can make this check before the Qthread, but this problem would probably also happen with the rest of the emits in the Qthread (I just had't caught it), and i'd like to avoid it and know what's wrong...


About the code in run: the check box is multi-purpose to save and to load password (the text within it changes accordingly: if there is an available password, it gets auto ticked into use saved; if user unticks, it changes to "save password" option); The @ or . Not in usr.txt() is because user must be an email. The wp(wrongpass) is a label that displays 'please fill in both fields' or 'insert a valid email'.

Update: Using signals/slots to pass the variables did not fix my problem. But i fixed it, it occurred to me that maybe, the Qthread was emiting the signals to fast, so i added a time.sleep(1) in the if and the elif before emiting the signal, and it works perfectly! - Could this actually have been the cause?

wrong1man
  • 133
  • 2
  • 10
  • Accessing Qt GUI widgets from a thread is not safe. Please look into thread-safety in Qt. In it's current form, I suspect you will start getting random crashes as your program becomes more complex (and this may very well explain why you get random behaviour). – three_pineapples Apr 26 '18 at 00:50
  • Hi! I barely access the widgets, I just load them into the thread in the init function, then use the data in them (the loaded ones). In the end i emit a signal to the qt gui and make the changes withing the gui acording to the emitted signal – wrong1man Apr 26 '18 at 01:12
  • It doesn't batter that you "barely" access them. It is not thread safe to access any Qt widget from a thread. When you pass the objects to your `QThread`, you are just passing a reference to them. It doesn't make a copy. Qt thread safety is well documented, and you would be wise to research it and make your application thread safe. If you are unsure, please post a separate question regarding Qt thread safety as there are plenty of people here who can help you out with that. – three_pineapples Apr 26 '18 at 05:19
  • Ok! Thanks! I will look into it and post a new question if there is need for it! – wrong1man Apr 26 '18 at 09:43
  • Hey! from what i researched i should emit a signal (or more) from the main thread to the Qthread with the values that are going to be used there; is this safe? and btw, i can't emit these signals before starting the thread correct? – wrong1man Apr 26 '18 at 12:49
  • Hey, would using copy.deecopy of the variable (not the widget) be safe/legal? – wrong1man Apr 26 '18 at 13:51
  • @three_pineapples hey can you check the code revision and see if it is safe now? – wrong1man Apr 26 '18 at 14:53
  • It is very hard to tell, because your example is not a [mcve]. There are a bunch of undefined variables and I don't know where certain methods you are showing in your example exist, or what they are called by. To answer your previous question, you cannot deepcopy a Qt object. Sending simple Python datatypes to the Qthread (either as constructor arguments or via signals) is safe, but sending Qt objects is not. As long as you access the Qt object from the main thread, and send the results (for example a string containing a username or a password) to the thread, you will be safe. – three_pineapples Apr 27 '18 at 02:36
  • At what point are you supposedly sending widgets to the worker? From your code it looks like you're sending strings, i.e., self.userfield.text(). type(self.userfield.text()) will show that you're sending strings. Did you ever work this out? – user2188329 Apr 01 '21 at 21:58

1 Answers1

0

I think the problem is in self.check.text () ==" Save password " Note:

    self.checkBox = QtWidgets.QCheckBox('Save password', self)
    self.checkBox.toggle()
    self.checkBox.stateChanged.connect(self.changeTitle)
    self.stateCheckBox = 2

def changeTitle(self, state):
    self.stateCheckBox = state

Try iy:

from PyQt5        import QtWidgets, QtGui, QtCore
from PyQt5.QtCore import pyqtSignal, Qt

class Thread1(QtCore.QThread):
    done = pyqtSignal(int, str)
    def __init__(self, usr, paw, wp, check, parent=None):
        QtCore.QThread.__init__(self)
        self.user  = usr
        self.paw   = paw
        self.wp    = wp
        self.check = check

    def run (self):
        if (self.paw=='' or self.user=='' or self.wp=='') :
            txt=("Error: enter user or password or wrongpass")
            self.done.emit(4, txt)
        elif (self.paw != self.wp):
            txt=("Error:  password ")
            self.done.emit(4, txt)    
        elif self.check:
            txt=("Save data")
            self.done.emit(4, txt)             
        #elif ('@' not in self.user.text() or '.' not in self.user.text()) and self.check.text() == "Save password":
        # I do not know what it is
        elif ('@' not in self.user or '.' not in self.user) and self.check:
            print('\nif 2')
            txt=("loads of html code here")
            self.done.emit(4, txt)
            print('emited')
        else:
            txt=("do not save data")
            self.done.emit(4, txt)

class App(QtWidgets.QWidget):
    def __init__(self, parent=None):
        QtWidgets.QWidget.__init__(self, parent)

        self.userfield = QtWidgets.QLineEdit(self)
        self.userfield.setPlaceholderText("userfield")
        self.pwfield = QtWidgets.QLineEdit(self)
        self.pwfield.setPlaceholderText("pwfield")
        self.wrongpass = QtWidgets.QLineEdit(self)
        self.wrongpass.setPlaceholderText("wrongpass")        

        self.checkBox = QtWidgets.QCheckBox('Save password', self)
        self.checkBox.toggle()
        self.checkBox.stateChanged.connect(self.changeTitle)
        self.stateCheckBox = 2

        self.but1 = QtWidgets.QPushButton("testf", self)
        self.but1.clicked.connect(self.testf)

        self.label = QtWidgets.QLabel("", self)

        self.grid1 = QtWidgets.QGridLayout()
        self.grid1.addWidget(self.userfield, 0,  0, 1, 20)
        self.grid1.addWidget(self.pwfield,   1,  0, 1, 20)
        self.grid1.addWidget(self.wrongpass, 2,  0, 1, 20)
        self.grid1.addWidget(self.checkBox,  3,  0, 1, 20)
        self.grid1.addWidget(self.but1,      4,  0, 1, 20)
        self.grid1.addWidget(self.label,     6,  0, 1, 20)
        self.grid1.setContentsMargins(10, 10, 10, 10)
        self.setLayout(self.grid1)


    def testf(self):                   # starter function - called by pushbutton
        #self.myThread = thread(self.userfield,self.pwfield, self.wrongpass, self.checkBox)

        #self.myThread = Thread1(self.userfield, self.pwfield, self.wrongpass, self.stateCheckBox)
        self.myThread = Thread1(str(self.userfield.text()), 
                                str(self.pwfield.text()), 
                                str(self.wrongpass.text()), 
                                int(self.stateCheckBox))

        self.myThread.start()
        self.myThread.done.connect(self.threadrelay)

    def threadrelay(self, code, text): #decides what to do given the thread's emited signal arguments
        # I do not know what it is
        #self.movie.stop()
        #self.gif.hide()
        self.myThread.exit()
        if code == 3:
            print(text)
            print('trying to open main diag')
            self.Omaindialog(text)            # I do not know what it is
        elif code == 4:
            self.label.setText(text)
        else:
            self.errordiag(text, code)

    def changeTitle(self, state):
        self.stateCheckBox = state

if __name__ == "__main__":
    import sys
    app        = QtWidgets.QApplication(sys.argv)
    myapp      = App()
    myapp.show()
    sys.exit(app.exec_())               

enter image description here

S. Nick
  • 12,879
  • 8
  • 25
  • 33
  • Thanks for your answer! Let me explain a bit about the code in the main post – wrong1man Apr 26 '18 at 01:14
  • The problem is that if you make the same mistake multiple times the signal might not get registred some of those times and the loading animation stopped in the threadelay function doesn't stop! – wrong1man Apr 26 '18 at 01:20
  • @s-nick, please note that your example is not thread safe. Qt widgets cannot be access from a QThread, see [the Qt documentation](https://doc.qt.io/qt-5.10/threads-qobject.html) – three_pineapples Apr 26 '18 at 05:25
  • @three_pineapples, thanks for the explanation. So will it be thread safe? self.myThread = Thread1(str(self.userfield.text()), str(self.pwfield.text()), str(self.wrongpass.text()), int(self.stateCheckBox)) – S. Nick Apr 26 '18 at 16:04