title: Threading with QRunnable - Proper manner of sending bi-directional callbacks
body: <p><a href="https://www.learnpyqt.com/courses/concurrent-execution/multithreading-pyqt-applications-qthreadpool/" rel="nofollow noreferrer">From a tutorial</a>, I've seen that signals and slots can be used to make callbacks from a worker thread into the main GUI thread, but I'm uncertain how to establish bi-directional communication using signals and slots. The following is what I am working with:</p>
  <pre><code>class RespondedToWorkerSignals(QObject):
      callback_from_worker = pyqtSignal()
  
  class RespondedToWorker(QRunnable):
      def __init__(self, func, *args, **kwargs):
          super(RespondedToWorker, self).__init__()
          self._func = func
          self.args = args
          self.kwargs = kwargs
          self.signals = RespondedToWorkerSignals()
          self.kwargs['signal'] = self.signals.callback_from_worker
          print(&quot;Created a responded-to worker&quot;)
  
      @pyqtSlot()
      def run(self):
          self._func(*self.args, **self.kwargs)
  
      @pyqtSlot()
      def acknowledge_callback_in_worker(self):
          print(&quot;Acknowledged Callback in Worker&quot;)
  
  class MainWindow(QMainWindow):
  
      # Signal meant to connect to a slot present within a worker
      mainthread_callback_to_worker = pyqtSignal()
  
      def __init__(self, *args, **kwargs):
          super(MainWindow, self).__init__(*args, **kwargs)
          # Quick UI setup
          w, lay = QWidget(), QVBoxLayout()
          w.setLayout(lay)
          self.setCentralWidget(w)
          self.timer_label = QLabel(&quot;Timer Label&quot;)
          lay.addWidget(self.timer_label)
          self.btn_thread_example = QPushButton(&quot;Push Me&quot;)
          self.btn_thread_example.pressed.connect(self.thread_example)
          lay.addWidget(self.btn_thread_example)
          self.threadpool = QThreadPool()
          self.show()
  
          # Set up QTimer to continue in the background to help demonstrate threading advantage
          self.counter = 0
          self.timer = QTimer()
          self.timer.setInterval(1000)
          self.timer.timeout.connect(self.recurring_timer)
          self.timer.start()
  
      @pyqtSlot()
      def do_something(self, signal):
          # signal argument will be the callback_from_worker and it will emit to acknowledge_callback_in_mainthread
          print(&quot;do_something is sleeping briefly. Try to see if you get a locked widget...&quot;)
          time.sleep(7)
          signal.emit()
  
      @pyqtSlot()
      def acknowledge_callback_in_mainthread_and_respond(self):
          # this function should respond to callback_from_worker and emit a response
          print(&quot;Acknowledged Callback in Main&quot;)
          self.mainthread_callback_to_worker.emit()
  
      def thread_example(self):
          print(&quot;Beginning thread example&quot;)
          worker = RespondedToWorker(self.do_something)
          worker.signals.callback_from_worker.connect(self.acknowledge_callback_in_mainthread_and_respond)
      # self.mainthread_callback_to_worker.connect(worker.acknowledge_callback_in_worker) # &lt;-- causes crash
  
      def recurring_timer(self):
          self.counter += 1
          self.timer_label.setText(f&quot;Counter: {self.counter}&quot;)
  
  if __name__ == '__main__':
      app = QApplication(sys.argv)
      win = MainWindow()
      app.setStyle(&quot;Fusion&quot;)
      win.show()
      sys.exit(app.exec())
  </code></pre>
  <p>At the current moment, the script can make the second thread and send a signal to the main GUI. I'd like the GUI to send a response signal back to the worker thread. I'm also uncertain why connecting the main/GUI's signal <code>mainthread_callback_to_worker</code> causes a crash (see the commented-out line).</p>
  <p>I understand that one workaround would be for <code>do_something</code> to return some value and then use it inside the worker as &quot;acknowledgement&quot;. But I'd like to know the solution using signals &amp; slots, if possible.</p>