Python PyQt:moveToThread在对插槽使用partial()时不工作

Python PyQt:moveToThread在对插槽使用partial()时不工作,python,multithreading,qt,pyqt,pyqt4,Python,Multithreading,Qt,Pyqt,Pyqt4,我正在构建一个小型GUI应用程序,它运行一个生产者(工作者),GUI根据需要使用输出并绘制它(使用pyqtgraph) 因为producer是一个阻塞函数(运行需要一段时间),所以我(假设)将它移动到了自己的线程中 从生产者调用QThread.currentThreadId()时,它输出与主GUI线程相同的编号。因此,首先执行辅助线程,然后执行所有绘图函数调用(因为它们在同一线程的事件队列中排队)。我怎样才能解决这个问题 使用部分组件运行的示例: gui thread id 1406654536

我正在构建一个小型GUI应用程序,它运行一个生产者(工作者),GUI根据需要使用输出并绘制它(使用pyqtgraph)

因为producer是一个阻塞函数(运行需要一段时间),所以我(假设)将它移动到了自己的线程中

从生产者调用QThread.currentThreadId()时,它输出与主GUI线程相同的编号。因此,首先执行辅助线程,然后执行所有绘图函数调用(因为它们在同一线程的事件队列中排队)。我怎样才能解决这个问题

使用部分组件运行的示例:

gui thread id 140665453623104
worker thread id: 140665453623104
这是我的全部代码:

from PyQt4 import QtCore, QtGui
from PyQt4.QtCore import pyqtSignal
import pyqtgraph as pg
import numpy as np

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):

    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.moveToThread(self.workerThread)
        self.worker.termino.connect(self.setTermino)
        # 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.btn.clicked.connect(self.graficar)

        self.workerThread.start()
        self.show()

    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:
            QtCore.QTimer.singleShot(1, self.graficar)

if __name__ == '__main__':

    app = QtGui.QApplication([])
    window = MainWindow()

    QtCore.QTimer.singleShot(0, window.run);
    sys.exit(app.exec_())

问题是Qt试图根据
插槽
所在的线程选择连接类型(当您调用
signal.connect(slot)
)。因为您已经用
partial
将插槽包装在QThread中,所以您要连接到的插槽位于主线程(GUI线程)中。您可以覆盖连接类型(作为
connect()
的第二个参数),但这没有帮助,因为由
partial
创建的方法将始终存在于主线程中,因此将连接类型设置为by没有帮助

我能看到的唯一解决方法是设置中继信号,其唯一目的是有效地将不带参数的发出信号(例如按钮上单击的信号)更改为带一个参数的信号(您的
m
参数)。这样您就不需要在QThread中用
partial()
包装插槽

代码如下。我在主窗口类中创建了一个名为“relay”的带有一个参数(一个int)的信号。单击的按钮
信号连接到主窗口类中的一个方法,此方法有一行代码,它发出我创建的自定义信号。您可以扩展此方法(
relay\u signal()
)获取要作为
m
(在本例中为500)传递给QThread的整数,从您喜欢的任何位置

下面是代码:

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_())
我还修改了
graficar
方法,以便在队列中仍有数据时继续打印(即使在线程终止后)。我想这可能就是为什么您需要QThread中的
时间。sleep
,现在它也被删除了


另外,关于您在代码中对放置
moveToThread
的位置的评论,现在的位置是正确的。应该在将QThread插槽连接到信号的调用之前,其原因将在堆栈溢出帖子中讨论:

非常感谢您的详细解释!