Search code examples
pythonmultithreadingpyqtqueuesignals

Connect 2 signals/ threads in one function with signals/emit


My application depends on getting results from two different threads that send a integer if the calculation in the thread concludes. This happens in a while True loop, therefor .join() will not work. The Idea is that two constant camera feeds are connected to a UI with a position finding allgorithm. I'm trying to connect a third function in the parent class in such a way, that its called every time both signals have been emited.


Solution

  • Here is a minimal working example of what I want was able to achieve. The self.number is used to keep track of the function call. The solution is using two seperate Queue objects from the queue package and a seperate custom signla class. The queues are initialized in the MainWindow() class. Allthough not used, the error and result signals show the versatility of custom signals. The rest of the init is only used to set up the UI with a button and a lable.

    In the start_thread() function the signals from the thread class are connected to the does_something() function, that is called every time the process is finished. The Emits_something() class puts a value (in this case a integer) in the queue and finishes the thread. This signal is then used to call the does_something() function, which only sets the lable text if both queues are not empty. This only allows for a function execution if both thread have finished their calculations. It doesn't matter which signal is emited first. They can even be emited simultaneously.

    # -*- coding: utf-8 -*-
    
    
    from queue import Queue
    import sys
    from PyQt5 import QtCore as qtc
    import numpy as np
    from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton, QLabel, QVBoxLayout, QWidget
    
    class WorkerSignals(qtc.QObject):
        finished = qtc.pyqtSignal()
        error = qtc.pyqtSignal(int)
        result = qtc.pyqtSignal(object)
    
    class MainWindow(QMainWindow):
        def __init__(self,*args, **kwargs):
            super(MainWindow, self).__init__(*args, **kwargs)
            self.threadpool = qtc.QThreadPool()
            
            ## number that checks number of function call
            self.number = 0
            
            self.queue_one = Queue()
            self.queue_two = Queue()
            
            self.setWindowTitle("Does something")
            self. pagelayout = QVBoxLayout()
            self.boxlayout = QVBoxLayout()
            self.pagelayout.addLayout(self.boxlayout)
    
            
            self.button = QPushButton("Does something")
            self.lable = QLabel('Says something')
    
            self.boxlayout.addWidget(self.button)
            self.boxlayout.addWidget(self.lable)
            # self.setCentralWidget(self.boxlayout)
            
            self.button.clicked.connect(self.start_thread)
            self.widget = QWidget()
            self.widget.setLayout(self.pagelayout)
            self.setCentralWidget(self.widget)
        
        def start_thread(self):
            worker_one = Emits_something(self.queue_one)
            worker_two = Emits_something(self.queue_two)
            
            worker_one.signals.finished.connect(self.does_something)
            worker_two.signals.finished.connect(self.does_something)
            
            self.threadpool.start(worker_one)
            self.threadpool.start(worker_two)
    
        
        def does_something(self):
            print(self.number)
            self.number +=1
            if not self.queue_one.empty() and not self.queue_two.empty():
                val_one = self.queue_one.get()
                val_two = self.queue_two.get()
                self.lable.setText(f"{val_one+val_two}")
        
    class Emits_something(qtc.QRunnable):
        def __init__(self,queue, *args,**kwargs):
            super(Emits_something, self).__init__(*args, **kwargs)
            self.signals = WorkerSignals()
            self.queue = queue
    
        @qtc.pyqtSlot()
        def run(self):
            val = np.random.randint(0,21) 
            self.queue.put(val)
            self.signals.finished.emit()
            
    
    if __name__ =='__main__':
        app = QApplication(sys.argv)
        w = MainWindow()
        w.show()
        app.exec_()
        sys.exit(app.exec())