Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. Qt for Python
  4. GUI very slow when multithreading PyQt
Qt 6.12 LTS is out! See what's new in the release blog

GUI very slow when multithreading PyQt

Scheduled Pinned Locked Moved Unsolved Qt for Python
13 Posts 5 Posters 9.4k Views 2 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • M Offline
    M Offline
    mayaduchia
    wrote on last edited by
    #1
    This post is deleted!
    JonBJ 1 Reply Last reply
    0
    • M mayaduchia

      This post is deleted!

      JonBJ Offline
      JonBJ Offline
      JonB
      wrote on last edited by JonB
      #2

      @mayaduchia
      Hello and welcome.

      While waiting for someone to tell you how best to write this from Python:

      • Calling QApplication.processEvents() in a tight loop is "costly". And you never get out of the onControllerOn() slot, your whole UI now runs on the processEvents() instead of allowing it back to the normal, top-level Qt event loop.

      • What does your #get input from controller in a tight loop do? Yes it is in a secondary thread, but if that thread is constantly busy it will impact time for the UI thread.

      1 Reply Last reply
      1
      • M Offline
        M Offline
        mayaduchia
        wrote on last edited by
        #3

        @JonB thank you for your comment. I added the QApplication.processEvents() because otherwise the while loop would have a delay when updating the reportProgressxyz() function. I don't have any other solution for it (as far as I know). Is there another option that replaces the QApplication.processEvents()?
        And regarding the #get input from controller, it is where I update my GUI (I am changing position in a plot) using the input of the controller at every iteration of the while loop. I don't know if the explanation is clear, if not please do tell me.

        JonBJ 1 Reply Last reply
        0
        • M mayaduchia

          @JonB thank you for your comment. I added the QApplication.processEvents() because otherwise the while loop would have a delay when updating the reportProgressxyz() function. I don't have any other solution for it (as far as I know). Is there another option that replaces the QApplication.processEvents()?
          And regarding the #get input from controller, it is where I update my GUI (I am changing position in a plot) using the input of the controller at every iteration of the while loop. I don't know if the explanation is clear, if not please do tell me.

          JonBJ Offline
          JonBJ Offline
          JonB
          wrote on last edited by JonB
          #4

          @mayaduchia
          Usually we basically never call processEvents() for choice, the top-level event loop does the event processing and we leave it to it. processEvents() can be used in situations where code would block/take a long time, but there are issues and it is not desirable.

          And regarding the #get input from controller, it is where I update my GUI (I am changing position in a plot) using the input of the controller at every iteration of the while loop.

          Nope! This loop is running in your non-UI, secondary worker thread. Qt has an absolute rule that UI operations can only be performed in the main UI thread, else havoc/crash. You must access the UI directly in any other thread, at most such a thread can emit signals which the main UI thread has a slot on, so that the main thread only performs UI operations in response.

          When I integrated the controller module, I created a while loop to handle all controller inputs, but the application crashed. So to prevent the application from crashing, I used the multithreading solution

          Truly out of the frying pan but into the fire! ;-)

          Basically it is a "shame" that you are having to use threads at all. Half of beginners rush in where angels fear to tread, threading in Qt (and elsewhere!) is about the hardest thing to get right and to be avoided if at all possible, certainly if you are a beginner.

          You are going to want to start by understanding how your "controller/joystick", and the "pygame library" work and interact. How are joystick events delivered? If the pygame stuff is blocking/synchronous, you might run that alone in a thread and have it just emit signals which the main UI thread receives and deals with.

          M 1 Reply Last reply
          1
          • JonBJ JonB

            @mayaduchia
            Usually we basically never call processEvents() for choice, the top-level event loop does the event processing and we leave it to it. processEvents() can be used in situations where code would block/take a long time, but there are issues and it is not desirable.

            And regarding the #get input from controller, it is where I update my GUI (I am changing position in a plot) using the input of the controller at every iteration of the while loop.

            Nope! This loop is running in your non-UI, secondary worker thread. Qt has an absolute rule that UI operations can only be performed in the main UI thread, else havoc/crash. You must access the UI directly in any other thread, at most such a thread can emit signals which the main UI thread has a slot on, so that the main thread only performs UI operations in response.

            When I integrated the controller module, I created a while loop to handle all controller inputs, but the application crashed. So to prevent the application from crashing, I used the multithreading solution

            Truly out of the frying pan but into the fire! ;-)

            Basically it is a "shame" that you are having to use threads at all. Half of beginners rush in where angels fear to tread, threading in Qt (and elsewhere!) is about the hardest thing to get right and to be avoided if at all possible, certainly if you are a beginner.

            You are going to want to start by understanding how your "controller/joystick", and the "pygame library" work and interact. How are joystick events delivered? If the pygame stuff is blocking/synchronous, you might run that alone in a thread and have it just emit signals which the main UI thread receives and deals with.

            M Offline
            M Offline
            mayaduchia
            wrote on last edited by
            #5

            @JonB Thank you so much for your answer. I will look into it.

            1 Reply Last reply
            0
            • SGaistS Offline
              SGaistS Offline
              SGaist
              Lifetime Qt Champion
              wrote on last edited by
              #6

              Hi,

              I don't remember whether the module is available with Python but Qt 5 at least had the Qt Gamepad.

              Interested in AI ? www.idiap.ch
              Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

              M 1 Reply Last reply
              0
              • S Offline
                S Offline
                SimonSchroeder
                wrote on last edited by
                #7

                I don't know Python, but only C++. Maybe someone can help to translate, if necessary.

                Your problem is your control loop running processEvents(). This has terrible performance (I used to do it in the same way, but notice something like a 100x performance hit). You are most likely correct that reportProgressxyz needs to be called inside the GUI thread. The easiest solution is for your controller thread to emit a signal (from your separate thread) which is connected to a slot in the GUI thread. This just works.

                You should also make sure that reportProgressxyz is not called to often. This will also slow down the GUI significantly. I personally have the problem with a regular progress bar. What I started adapting is the use of QTime (C++):

                QTime time;
                time.start();
                while(...)
                {
                  if(time.elapsed() >= 50) // use whatever time fits your case best instead of 50ms
                  {
                    //reportProgressxyz
                    time.restart();
                  }
                }
                

                50ms is good for my progress bar, but not suitable in your case. 16ms is the time for a single frame at 60Hz. So, maybe do something like 8ms or 4ms.

                There is another way to execute reportProgressxyz inside the GUI thread from another thread (again C++):

                QMetaObject::invokeMethod(qApp, [&](){ this->reportProgressxyz(worker->x, worker->y, worker->z); });
                

                For performance: never block the GUI thread. Even not when calling processEvents().

                M 1 Reply Last reply
                1
                • SGaistS SGaist

                  Hi,

                  I don't remember whether the module is available with Python but Qt 5 at least had the Qt Gamepad.

                  M Offline
                  M Offline
                  mayaduchia
                  wrote on last edited by
                  #8

                  @SGaist There is no Qt Gamepad support on PyQt5

                  1 Reply Last reply
                  0
                  • S SimonSchroeder

                    I don't know Python, but only C++. Maybe someone can help to translate, if necessary.

                    Your problem is your control loop running processEvents(). This has terrible performance (I used to do it in the same way, but notice something like a 100x performance hit). You are most likely correct that reportProgressxyz needs to be called inside the GUI thread. The easiest solution is for your controller thread to emit a signal (from your separate thread) which is connected to a slot in the GUI thread. This just works.

                    You should also make sure that reportProgressxyz is not called to often. This will also slow down the GUI significantly. I personally have the problem with a regular progress bar. What I started adapting is the use of QTime (C++):

                    QTime time;
                    time.start();
                    while(...)
                    {
                      if(time.elapsed() >= 50) // use whatever time fits your case best instead of 50ms
                      {
                        //reportProgressxyz
                        time.restart();
                      }
                    }
                    

                    50ms is good for my progress bar, but not suitable in your case. 16ms is the time for a single frame at 60Hz. So, maybe do something like 8ms or 4ms.

                    There is another way to execute reportProgressxyz inside the GUI thread from another thread (again C++):

                    QMetaObject::invokeMethod(qApp, [&](){ this->reportProgressxyz(worker->x, worker->y, worker->z); });
                    

                    For performance: never block the GUI thread. Even not when calling processEvents().

                    M Offline
                    M Offline
                    mayaduchia
                    wrote on last edited by
                    #9

                    @SimonSchroeder Thank you for your comment. I tried to use the QTime solution and removed QApplication.processEvents() from the while loop, it didn't work, the application crashed right away. Also when I used a signal and consider reportProgressxyz as a slot, for some reason the UI don't get updated.

                    jsulmJ 1 Reply Last reply
                    0
                    • M mayaduchia

                      @SimonSchroeder Thank you for your comment. I tried to use the QTime solution and removed QApplication.processEvents() from the while loop, it didn't work, the application crashed right away. Also when I used a signal and consider reportProgressxyz as a slot, for some reason the UI don't get updated.

                      jsulmJ Offline
                      jsulmJ Offline
                      jsulm
                      Lifetime Qt Champion
                      wrote on last edited by
                      #10

                      @mayaduchia said in GUI very slow when multithreading PyQt:

                      the application crashed right away

                      What does the debugger tell you?
                      Please post your current code...

                      https://forum.qt.io/topic/113070/qt-code-of-conduct

                      M JonBJ 2 Replies Last reply
                      0
                      • jsulmJ jsulm

                        @mayaduchia said in GUI very slow when multithreading PyQt:

                        the application crashed right away

                        What does the debugger tell you?
                        Please post your current code...

                        M Offline
                        M Offline
                        mayaduchia
                        wrote on last edited by mayaduchia
                        #11

                        @jsulm here's my code:

                        class Window(QMainWindow):
                        	 def init(self):
                        		 #create button for controller
                        		 controllerOn = QPushButton("Controller On")
                        		 controllerOn.clicked.connect(self.onControllerOn)
                        		 #create frame to display plot
                        		 self.frame = QtWidgets.QFrame()
                        		 vbox = QVBoxLayout()
                        		 self.plotter = QtInteractor(self.frame)
                        		 vbox.addWidget(self.plotter.interactor)
                        		 
                        	def onControllerOn(self):
                        		if(self.controllerFirstConnexion == True):
                        			#create thread to handle controller inputs
                        			self.thread = QThread()
                        			self.worker = Worker()
                        			self.worker.moveToThread(self.thread)
                        			#do all the connections
                        			self.thread.started.connect(self.worker.run)
                        			self.worker.finished.connect(self.thread.quit)
                        			self.worker.finished.connect(self.worker.deleteLater)
                        			self.thread.finished.connect(self.thread.deleteLater)
                        			#start thread
                        			self.thread.start()
                        			
                        		while(self.worker.controllerConnected == True):
                        			QApplication.processEvents()
                        			self.reportProgressxyz(self.worker.x, self.worker.y, self.worker.z)
                        
                        	def reportProgressxyz(self, x, y, z):
                        		#update point in plot
                        		points = np.array([x,y,z])
                                self.plotter.add_points(points, render_points_as_spheres=True, point_size=8.0, color ='r')
                        		
                        		
                        class Worker(QObject):
                            def run(self):
                        		pygame.init()
                        		joysticks = []
                        
                        		#connect controller
                        		for i in range(0, pygame.joystick.get_count()):
                        			joysticks.append(pygame.joystick.Joystick(i))
                        			joysticks[-1].init()
                        			
                        		self.controllerConnected = True
                        		if pygame.joystick.get_count() == 1:
                        			while (self.controllerConnected == True) :
                        			  for event in pygame.event.get():
                                              if event.type == pygame.JOYAXISMOTION:
                                                if(event.axis == 5):
                                                    if (event.value>0.9):
                                                      self.rightTrigger = 1
                                                    if(event.value>-0.5 and event.value<0.9):
                                                        self.rightTrigger = 0  
                                                if(event.axis == 4):
                                                    if (event.value>0.9):
                                                      self.leftTrigger = 1
                                                    if(event.value>-0.5 and event.value<0.9):
                                                        self.leftTrigger = 0  
                                                  
                                                while self.rightTrigger == 1 and self.leftTrigger == 0:
                                                self.x+=1
                                                while self.rightTrigger == 0 and self.leftTrigger == 1:
                                                self.y+=1
                                                while self.rightTrigger == 1 and self.leftTrigger == 1:
                                                self.x+=1
                                                self.y+=1
                        
                        jsulmJ 1 Reply Last reply
                        0
                        • jsulmJ jsulm

                          @mayaduchia said in GUI very slow when multithreading PyQt:

                          the application crashed right away

                          What does the debugger tell you?
                          Please post your current code...

                          JonBJ Offline
                          JonBJ Offline
                          JonB
                          wrote on last edited by
                          #12

                          @mayaduchia

                          @jsulm said in GUI very slow when multithreading PyQt:

                          What does the debugger tell you?

                          1 Reply Last reply
                          0
                          • M mayaduchia

                            @jsulm here's my code:

                            class Window(QMainWindow):
                            	 def init(self):
                            		 #create button for controller
                            		 controllerOn = QPushButton("Controller On")
                            		 controllerOn.clicked.connect(self.onControllerOn)
                            		 #create frame to display plot
                            		 self.frame = QtWidgets.QFrame()
                            		 vbox = QVBoxLayout()
                            		 self.plotter = QtInteractor(self.frame)
                            		 vbox.addWidget(self.plotter.interactor)
                            		 
                            	def onControllerOn(self):
                            		if(self.controllerFirstConnexion == True):
                            			#create thread to handle controller inputs
                            			self.thread = QThread()
                            			self.worker = Worker()
                            			self.worker.moveToThread(self.thread)
                            			#do all the connections
                            			self.thread.started.connect(self.worker.run)
                            			self.worker.finished.connect(self.thread.quit)
                            			self.worker.finished.connect(self.worker.deleteLater)
                            			self.thread.finished.connect(self.thread.deleteLater)
                            			#start thread
                            			self.thread.start()
                            			
                            		while(self.worker.controllerConnected == True):
                            			QApplication.processEvents()
                            			self.reportProgressxyz(self.worker.x, self.worker.y, self.worker.z)
                            
                            	def reportProgressxyz(self, x, y, z):
                            		#update point in plot
                            		points = np.array([x,y,z])
                                    self.plotter.add_points(points, render_points_as_spheres=True, point_size=8.0, color ='r')
                            		
                            		
                            class Worker(QObject):
                                def run(self):
                            		pygame.init()
                            		joysticks = []
                            
                            		#connect controller
                            		for i in range(0, pygame.joystick.get_count()):
                            			joysticks.append(pygame.joystick.Joystick(i))
                            			joysticks[-1].init()
                            			
                            		self.controllerConnected = True
                            		if pygame.joystick.get_count() == 1:
                            			while (self.controllerConnected == True) :
                            			  for event in pygame.event.get():
                                                  if event.type == pygame.JOYAXISMOTION:
                                                    if(event.axis == 5):
                                                        if (event.value>0.9):
                                                          self.rightTrigger = 1
                                                        if(event.value>-0.5 and event.value<0.9):
                                                            self.rightTrigger = 0  
                                                    if(event.axis == 4):
                                                        if (event.value>0.9):
                                                          self.leftTrigger = 1
                                                        if(event.value>-0.5 and event.value<0.9):
                                                            self.leftTrigger = 0  
                                                      
                                                    while self.rightTrigger == 1 and self.leftTrigger == 0:
                                                    self.x+=1
                                                    while self.rightTrigger == 0 and self.leftTrigger == 1:
                                                    self.y+=1
                                                    while self.rightTrigger == 1 and self.leftTrigger == 1:
                                                    self.x+=1
                                                    self.y+=1
                            
                            jsulmJ Offline
                            jsulmJ Offline
                            jsulm
                            Lifetime Qt Champion
                            wrote on last edited by
                            #13

                            @mayaduchia I don't see you doing this: "Also when I used a signal and consider reportProgressxyz as a slot".
                            And you also ignored my question regarding debugger...

                            https://forum.qt.io/topic/113070/qt-code-of-conduct

                            1 Reply Last reply
                            0

                            • Login

                            • Login or register to search.
                            • First post
                              Last post
                            0
                            • Categories
                            • Recent
                            • Tags
                            • Popular
                            • Users
                            • Groups
                            • Search
                            • Get Qt
                            • Unsolved