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.5k 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 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