当前位置: 首页 > 面试题库 >

PyQt:当对插槽使用partial()时,moveToThread不起作用

堵琨
2023-03-14
问题内容

我正在构建一个运行生产者(工人)的小型GUI应用程序,该GUI按需使用输出并将其绘制(使用pyqtgraph)。

由于生产者是一个阻塞函数(需要一段时间才能运行),我(据说)将其移到了自己的线程中。

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

使用局部运行的示例:

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)根据slot存在的线程来选择连接类型(当您调用时)。因为您已经partial用来将QThread封装在插槽中,所以要连接的插槽位于MainThread(GUI线程)中。
)。您可以覆盖连接类型(作为的第二个参数,connect()但这没有帮助,因为by创建的方法partial将始终存在于MainThread中,因此将连接类型设置为byQt.QueuedConnection无效。

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

代码如下。我已经在主Windows类中创建了一个带有称为“
relay”的参数(一个int)的信号。按钮clicked信号连接到主窗口类中的一个方法,该方法有一行代码,可发出我创建的自定义信号。您可以扩展此方法(relay_signal()),以从任意位置获取要传递给QThread的整数m(本例中为500)!

所以这是代码:

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如果队列中仍然有数据,我还修改了该方法以继续绘制(即使在线程终止之后)。我认为这可能就是为什么您需要time.sleepQThread中的原因,该问题现在也已删除。

另外,关于代码中有关放置moveToThread位置(现在 正确)的 注释 也是正确的
。应该在将QThread插槽连接到信号的调用之前,并且在此堆栈溢出文章中讨论了这样做的原因:PyQt:将信号连接到插槽以启动后台操作



 类似资料:
  • 问题内容: 我读了这篇文章《如何真正地,真正地使用QThreads》。完整说明,它说而不是子类qthread和重新实现run(),应使用moveToThread(QThread *)使用moveToThread将QObject推送到QThread实例上 这是C ++示例,但我不知道如何将其转换为python代码。 我一直在使用这种方法来生成qthread,但是如您所见,它使用的是不推荐的方式。我如

  • 本文向大家介绍vue 使用插槽分发内容操作示例【单个插槽、具名插槽、作用域插槽】,包括了vue 使用插槽分发内容操作示例【单个插槽、具名插槽、作用域插槽】的使用技巧和注意事项,需要的朋友参考一下 本文实例讲述了vue 使用插槽分发内容操作。分享给大家供大家参考,具体如下: 单个插槽 除非子组件模板包含至少一个 <slot> 插口,否则父组件的内容将会被丢弃。当子组件模板只有一个没有属性的插槽时,父

  • 问题内容: 我构建了一个PyQt5 GUI来进行一些selenium测试。除PyQt进度条外,其他所有操作均按预期进行。 在下面的第一个示例中,当我使用Selenium浏览器时,最后,当浏览器关闭时,进度条只会跳到100%。但是,selenium工作正常。 但是,在下面的此版本中,在Selenium浏览器被注释掉的情况下,进度条可以按预期工作。 问题答案: 阻塞任务与在其中执行GUI的事件循环不友

  • 当我在spring aop拦截类上使用注释时,我使用了@target limit匹配方法。但调试时,会提示以下错误。 我的切入点是这样的 其中TimeIt是注释: 我把切点改成能跑了 我不太理解的是,MetricsFilter 类没有这个注释,那么为什么要生成代理呢?任何人都可以帮我吗?谢谢。

  • 1. 前言 本小节我们将介绍如何使用插槽slot,包括默认插槽、具名插槽、作用域插槽。插槽可以使组件的模版变得多样性,让用户在使用组件时可以自定义传入模版内容。在复杂组件中,当我们在使用多个插槽的时候将会是一个难点。但是也不必担心,只要我们将每个插槽类型的使用方法学透,相信面对任何复杂插槽的使用的时候都可以游刃有余。 2. 慕课解释 Vue 实现了一套内容分发的 API,将 元素作为承载分发内容的

  • 数据库: SQL查询 搜索 当我使用“Like”查询时,一切都正常,但是当我查找John时,它会显示John和Johnson的两个结果,因为它们是相似的。但当我搜索约翰时,它必须只显示约翰(而不是约翰逊)的结果。 到目前为止,我试过: 但它不起作用LogCat: 请给我一个提示。