Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

Multithreading puzzle



  • Hi all,

    I am relatively new to Python and Qt. I have recently (3 months ago) started learning python, and I am getting the hang of it.

    I am not one to read the manual beginning to end; I sort of dove off in the deep end. My first objective is to create a car media player, with all common functions like radio, media player, bluetooth, etc. My progress so far is that I have built the GUI in PyQt5(just moved to Qt6), programmed the Qpushbuttons etc, and I have made a start on the volume control. This is where I am getting stumped. I plan to use classic On-screen-display for the volume and other direct influence settings (like brightness). I have managed to get the volume to neatly decrease and increase, but when I release the button, I need the source buttons to immediately be re-enabled and listen for presses, while in a parallel thread a method is called to wait 3 seconds, and then remove the volume widget from screen (like in older tvs). I have studied QThread and tried a few times, but it keeps botching and I have had numerous different errors. I have included the code without the attempt for multithreading and I have annotated important parts:

    class Carma(QtWidgets.QWidget):
    
        def __init__(self,init):
            super().__init__()
    
            # Setup main components and variables
            # -----------------------------------
            # *snipped out widget code, not important for this question
    
            self.init=init
            setSource()
    
    
        def src_button_active(self ,num):
            self.buttons_src[num].setStyleSheet("border-image: url(images/bezel-buttonp.png)") # changes image for button animation
            self.buttons_src[1].released.connect(lambda: self.radio(1))
            self.buttons_src[2].released.connect(lambda: self.mediaplayer(2))
            self.buttons_src[3].released.connect(lambda: self.btplayer(3))
            self.buttons_src[4].released.connect(lambda: self.equaliser(4))
            self.buttons_src[5].released.connect(lambda: self.settings(5))
    
        def setSource(self):    #set the source buttons to listen
            for x in range (1 ,6):
                self.buttons_src[x].pressed.connect(lambda: self.src_button_active(x))
            if self.init == True:
                self.setVolume()
    
        def setVolume(self): # set the volume buttons to listen
            self.buttons_src[6].setStyleSheet("border-image: url(images/bezel-button.png)") # resets button animation
            self.buttons_src[6].pressed.connect(lambda: self.voldn())
            self.buttons_src[6].released.connect(lambda: self.volhide(2))                   # or should I use clicked here?
            self.buttons_src[7].setStyleSheet("border-image: url(images/bezel-button.png)")
            self.buttons_src[7].pressed.connect(lambda: self.volup())
            self.buttons_src[7].released.connect(lambda: self.volhide(2))
            self.init = False
    
        def volhide(self,waittime): # This should be done parallel with the return to setVolume after volume button has been released. Has to terminate itself too.
            self.buttons_src[6].setStyleSheet("border-image: url(images/bezel-button.png")
            self.buttons_src[7].setStyleSheet("border-image: url(images/bezel-button.png")
            print ("volume hide waiting time")     #just debugging info
            time.sleep(waittime)
            self.osd_vol_widget.hide()
            print ("Volume widget hidden")
    
        def voldn(self):
            print("voldn pressed",self.volume)
            self.buttons_src[6].setStyleSheet("border-image: url(images/bezel-buttonp.png")
            if self.osd_vol_widget.isHidden():
                self.osd_vol_widget.show()
            self.volume -= self.volstep
            self.osd_vol_bar.setValue(self.volume)
            time.sleep(1/self.volrate)
    
        def volup(self):
            print("volup pressed",self.volume)
            self.buttons_src[7].setStyleSheet("border-image: url(images/bezel-buttonp.png")
            if self.osd_vol_widget.isHidden():
                self.osd_vol_widget.show()
            self.volume += self.volstep
            self.osd_vol_bar.setValue(self.volume)
            time.sleep(1/self.volrate)
    
    
    

    I would like to request that a volunteer mute the code to make volhide a parallel thread. It will also help me greatly in understanding Qt multithreading. I have succeeded in multithreading non-Qt code, i can drive 5 SPI displays simultaneously now.

    And of course, I appreciate any and all input on this matter. Thank you in advance.



  • @Paul_F
    Like every beginner here, who mostly seem to try to use threads from the outset, I would say: why are you wanting to use a thread? They are complex, and really often not needed, especially by beginners. And they always cause problems.

    while in a parallel thread a method is called to wait 3 seconds, and then remove the volume widget from screen (like in older tvs).

    Why any thread here? Use a QTimer. And in any case you will soon find that since Qt does not allow access to anything in the main UI thread from any other thread, without falling over in a big heap, you couldn't do what you said from another thread anyway....



  • @JonB Yes, I have already experienced that problem about the main UI, and if QTimer is the solution I will go with that.

    Do you have any suggestions for QTimer?

    Thank you.



  • @Paul_F said in Multithreading puzzle:

    Do you have any suggestions for QTimer?

    Like what? Use it to implement exactly what you wrote. Use QTimer::singleShot(), set it off for 3 seconds from whatever moment you want it to start (some button being pressed or whatever), have its slot remove the widget.

    BTW, don't ever use Python's time.sleep() in your UI code as you presently have.



  • Thnx again.

    I am learning. Can you explain why my use of time.sleep is unwanted?


  • Lifetime Qt Champion

    Hi,

    Because it will block the event loop and this freeze your application.



  • @Paul_F
    You have slots calling time.sleep(). While that is executing the Qt event loop won't run. Your UI will be blocked/unresponsive when the button is pressed causing this code to run. A QTimer will not do that. This is an event-driven approach.



  • @JonB

    Your explanation means a lot to me, thank you very much. I understand now. It is purely a Qt event loop thing. The time.sleep command was originally incorporated in methods I tried to call with Qthread, but I removed that code and left the time.sleep in. That probably explains it.


Log in to reply