PyQt: moveToThread does not work when using partial() for slot

后端 未结 1 1860
南旧
南旧 2020-12-07 04:40

I am building a small GUI application which runs a producer (worker) and the GUI consumes the output on demand and plots it (using pyqtgraph).

Since the producer is

相关标签:
1条回答
  • 2020-12-07 05:09

    The problem is that Qt attempts to choose the connection type (when you call signal.connect(slot)) based on the what thread the slot exists in. Because you have wrapped the slot in the QThread with partial, the slot you are connecting to resides in the MainThread (the GUI thread). You can override the connection type (as the second argument to connect() but that doesn't help because the method created by partial will always exist in the MainThread, and so setting the connection type to by Qt.QueuedConnection doesn't help.

    The only way around this that I can see is to set up a relay signal, the sole purpose of which is to effectively change an emitted signal with no arguments (eg the clicked signal from a button) to a signal with one argument (your m parameter). This way you don't need to wrap the slot in the QThread with partial().

    The code is below. I've created a signal with one argument (an int) called 'relay' in the main windows class. The button clicked signal is connected to a method within the main window class, and this method has a line of code which emits the custom signal I created. You can extend this method (relay_signal()) to get the integer to pass to the QThread as m (500 in this case), from where ever you like!

    So here is the code:

    from functools import partial
    from Queue import Queue
    import math
    import sys
    import time
    
    class Worker(QtCore.QObject):
    
        termino = pyqtSignal()
    
        def __init__(self, q=None, parent=None):
            super(Worker, self).__init__(parent) 
            self.q = q
    
        def run(self, m=30000):
            print('worker thread id: {}'.format(QtCore.QThread.currentThreadId()))
            for x in xrange(m):
                #y = math.sin(x)
                y = x**2
                #time.sleep(0.001) # Weird, plotting stops if this is not present...
                self.q.put((x,y,y))
            print('Worker finished')
    
            self.termino.emit()
    
    class MainWindow(QtGui.QWidget):
    
        relay = pyqtSignal(int)
    
        def __init__(self, parent=None):
            super(MainWindow, self).__init__(parent)
    
            self.q = Queue()
            self.termino = False       
    
            self.worker = Worker(self.q)
    
            self.workerThread = None
            self.btn = QtGui.QPushButton('Start worker')
            self.pw = pg.PlotWidget(self)
            pi = self.pw.getPlotItem()
            pi.enableAutoRange('x', True)
            pi.enableAutoRange('y', True)
            self.ge1 = pi.plot(pen='y')
            self.xs = []
            self.ys = []
    
            layout = QtGui.QVBoxLayout(self)
            layout.addWidget(self.pw)
            layout.addWidget(self.btn)
    
            self.resize(400, 400)
    
        def run(self):
            self.workerThread = QtCore.QThread()
            self.worker.termino.connect(self.setTermino)
            self.worker.moveToThread(self.workerThread)
            # moveToThread doesn't work here
            # self.btn.clicked.connect(partial(self.worker.run, 30000))
            # moveToThread will work here
            # assume def worker.run(self): instead of def worker.run(self, m=30000)
            #self.btn.clicked.connect(self.worker.run)        
            self.relay.connect(self.worker.run)
            self.btn.clicked.connect(self.relay_signal)
            self.btn.clicked.connect(self.graficar)
    
            self.workerThread.start()
            self.show()
    
        def relay_signal(self):
            self.relay.emit(500)
    
        def setTermino(self):
            self.termino = True
    
        def graficar(self):
            if not self.q.empty():
                e1,e2,ciclos = self.q.get()       
                self.xs.append(ciclos)
                self.ys.append(e1)
                self.ge1.setData(y=self.ys, x=self.xs)
    
            if not self.termino or not self.q.empty():
                QtCore.QTimer.singleShot(1, self.graficar)
    
    if __name__ == '__main__':
    
        app = QtGui.QApplication([])
        window = MainWindow()
    
        QtCore.QTimer.singleShot(0, window.run);
        sys.exit(app.exec_())
    

    I also modified the graficar method to continue plotting (even after the thread is terminated) if there is still data in the queue. I think this might be why you needed the time.sleep in the QThread, which is now also removed.

    Also regarding your comments in the code on where to place moveToThread, where it is now is correct. It should be before the call that connects the QThread slot to a signal, and the reason for this is discussed in this stack-overflow post: PyQt: Connecting a signal to a slot to start a background operation

    0 讨论(0)
提交回复
热议问题