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.... -
@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. -
Hi,
Because it will block the event loop and this freeze your application.
-
@Paul_F
You have slots callingtime.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. AQTimer
will not do that. This is an event-driven approach. -
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.