title: PyQt6: Calling worker object method makes program temporarily unresponsive
body: <p>I'm trying to replicate <a href="https://humanbenchmark.com/tests/sequence" rel="nofollow noreferrer">this</a> with Python and PyQt6. I have been successful in creating a working prototype of it.<br />
  <strong>The problem:</strong><br />
  <code>SequenceMemory.request_round</code> is a <code>PyQt6.QtCore.pyqtSignal</code> that is emitted when the user is to be displayed a new round for the game, be it at the start of the game or after a previous round. However, for some reason, when <code>request_round</code> is emitted, the GUI goes unresponsive for about half a second.<br />
  I've pin-pointed the problem to the <code>self.request_round.emit()</code> line in the <code>new_round()</code> method of the <code>SequenceMemory</code> class (after commenting out that line the GUI does not go unresponsive), but I can't seem to figure out why this occurs.<br />
  I thought the <code>sleep()</code> function in the <code>CreateSequenceWorker</code> class might be the cause but changing the integer passed to <code>sleep()</code> didn't change how long the GUI went unresponsive so I don't think that's the cause.</p>
  <pre class="lang-py prettyprint-override"><code>import sys
  import queue
  import random
  import itertools
  import functools
  from time import perf_counter
  
  
  from PyQt6 import QtWidgets as qtw
  from PyQt6 import QtGui as qtg
  from PyQt6 import QtCore as qtc
  
  
  class SequenceMemory(qtw.QMainWindow):
      
      SQUARE_PER_ROW = 3
  
      request_round = qtc.pyqtSignal()
      kill_thread = qtc.pyqtSignal()
  
      def __init__(self, *args, **kwargs):
          super().__init__(*args, **kwargs)
          
          self.sequence_history = []
          self.sequence_queue = queue.Queue()
          
          self.main_menu()
  
      def main_menu(self):
          start_button = qtw.QPushButton(&quot;Start&quot;)
          start_button.released.connect(self.start)
          self.main_widget = self.takeCentralWidget()
          self.setCentralWidget(start_button)
          self.show()
          
      def start(self):
          self.setup_GUI()
          self.create_board()
          self.setup_thread()
          self.start_button = self.takeCentralWidget()
          self.setCentralWidget(self.main_widget)
          self.new_round()
    
      def setup_GUI(self):
          self.main_widget = qtw.QWidget()
          self.main_widget.setStyleSheet(&quot;background-color: white;&quot;)
          self.main_widget.setLayout(qtw.QGridLayout())
          self.main_widget.layout().setSpacing(0)
          self.main_widget.layout().setContentsMargins(5, 5, 5, 10)
  
      def setup_thread(self):
          self.create_sequence_worker = CreateSequenceWorker()
          self.create_sequence_worker_thread = qtc.QThread()
          self.create_sequence_worker.moveToThread(
              self.create_sequence_worker_thread
          )
          self.create_sequence_worker_thread.start()
          
          self.create_sequence_worker.show_block.connect(self.update_board)
          self.create_sequence_worker.hide_block.connect(self.update_board)
  
          self.request_round.connect(self.create_sequence_worker.create_sequence)
  
  
      def create_board(self):
          self.buttons = [
              tuple(
                  qtw.QPushButton()
                  for _ in range(self.SQUARE_PER_ROW)
              )
              for _ in range(self.SQUARE_PER_ROW)
          ]
  
          for row, column in itertools.product(
              range(self.SQUARE_PER_ROW), repeat=2
          ):
              button = self.buttons[row][column]
              button.setStyleSheet(
                  &quot;&quot;&quot;
                  background-color: rgb(186, 196, 255);
                  border: 5px solid white;
                  border-radius: 15px;
                  &quot;&quot;&quot;
                  )
              button.setMinimumHeight(150)
              self.main_widget.layout().addWidget(
                  button, row, column, 1, 1
              )
  
      def update_board(self, mode: int, row: int, column: int):
          if mode:
              button = self.buttons[row][column]
              button.setStyleSheet(
                  &quot;&quot;&quot;
                  background-color: rgb(186, 255, 196);
                  border: 5px solid white;
                  border-radius: 15px;
                  &quot;&quot;&quot;
                  )
          else:
              button = self.buttons[row][column]
              button.setStyleSheet(
                  &quot;&quot;&quot;
                  background-color: rgb(186, 196, 255);
                  border: 5px solid white;
                  border-radius: 15px;
                  &quot;&quot;&quot;
                  )
          
      def new_round(self):
          self.request_round.emit()
  
      def closeEvent(self, a0: qtg.QCloseEvent):
          self.kill_thread.emit()
          self.create_sequence_worker_thread.quit()
          self.main_menu()
  
  
  class CreateSequenceWorker(qtc.QObject):
      queue_ready = qtc.pyqtSignal(queue.Queue)
      show_block = qtc.pyqtSignal(int, int, int)
      hide_block = qtc.pyqtSignal(int, int, int)
      
      def __init__(self, *args, **kwargs):
          super().__init__(*args, **kwargs)
          self.kill = False
          self.sequence_history = []
          self.sequence_queue = queue.Queue()
      
      def sleep(self, duration: float):
          start_time = perf_counter()
          while perf_counter() - start_time &lt; duration:
              if self.kill:
                  return False
          return True
  
      def create_sequence(self):
          self.sleep(0.5)
          for row, column in self.sequence_history:
              self.sequence_queue.put((row, column))
              
              self.show_block.emit(1, row, column)
              if self.sleep(0.5):
                  self.hide_block.emit(0, row, column)
              else:
                  return
  
          i = 0
          while i &lt; len(self.sequence_history) - self.sequence_queue.qsize() + 1:
              row, column = random.randrange(3), random.randrange(3)
              if self.sequence_history and (row, column) == self.sequence_history[-1]:
                  continue
  
              self.sequence_history.append((row, column))
              self.sequence_queue.put((row, column))
              i+=1
              
              self.show_block.emit(1, row, column)
              if self.sleep(0.5):
                  self.hide_block.emit(0, row, column)
              else:
                  return
  
          self.queue_ready.emit(self.sequence_queue)
  
  
      def kill_thread_event(self):
          self.kill = True
  
  
  class MainWindow(qtw.QWidget):
      def __init__(self, *args, **kwargs):
          super().__init__(*args, **kwargs)
          
          self.setMinimumHeight(450)
          self.setMinimumWidth(450)
          self.setWindowFlags(qtc.Qt.WindowType.FramelessWindowHint)
  
          self.setLayout(qtw.QVBoxLayout())
          self.layout().setContentsMargins(0, 0, 0, 0)
          self.widget = SequenceMemory()
          self.layout().addWidget(self.widget)
  
          self.show()
      
      def closeEvent(self, a0) -&gt; None:
          self.widget.close()
          return super().closeEvent(a0)
          
  if __name__ == &quot;__main__&quot;:
      app = qtw.QApplication(sys.argv)
      mw = MainWindow()
      sys.exit(app.exec())
  </code></pre>
  <p>Above is as minimal as I could get the program to be, without diverging too far away from the program's structure. The full program (in a single file) can be found <a href="https://pastebin.com/7FPuzmwq" rel="nofollow noreferrer">here</a>.</p>