多线程中的信号/槽

作者:
柴树杉[翻译] (chaishushan@gmail.com)
日期:
2008-01-05 于武汉
注解:
该文档根据Threading, Signals/Slots翻译,依照创作公用约定发布。

开始

在许多应用中都会遇到非常耗时的运算,在进行该类型运算时常常会影响程序正常的消息处理。 为了处理上述问题,我们可以将耗时的运算从GUI线程中移出来,单独放到一个work线程中。 这样的话,GUI则可以保持时刻响应。

下面的例子中,我们将演示如何运用多线程。在一个work线程中将完成绘制五角星的操作, 在绘制完成后将发射信号通知GUI线程进行显示。下面是程序的运行效果:

用户界面

首先导入相关模块。其中math和random模块在绘制五角星的时候需要:

import math, random, sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *

class Window(QWidget):

    def __init__(self, parent = None):
        QWidget.__init__(self, parent)
        self.thread = Worker()

主窗口从QWidget继承。同时创建一个Worker线程用于完成相关操作。

主窗口包含一个label,一个spin box(用于设置要绘制的五角星数目),一个按钮,线程输出的 图象在另一个QLabel(viewer)中显示。

        label = QLabel(self.tr("Number of stars:"))
        self.spinBox = QSpinBox()
        self.spinBox.setMaximum(10000)
        self.spinBox.setValue(1000)
        self.startButton = QPushButton(self.tr("&Start"))
        self.viewer = QLabel()
        self.viewer.setFixedSize(300, 300)

线程的finished()和terminated()信号被连接到self.updateUi,用于更新界面。output(QRect, QImage) 信号连接到addImage(),用于绘制单个的五角星。

        self.connect(self.thread, SIGNAL("finished()"), self.updateUi)
        self.connect(self.thread, SIGNAL("terminated()"), self.updateUi)
        self.connect(self.thread, SIGNAL("output(QRect, QImage)"), self.addImage)
        self.connect(self.startButton, SIGNAL("clicked()"), self.makePicture)

按钮的clicked()信号连接到makePicture(),用于启动work线程。每个部件元素通过grid布局管理器 来管理。然后设置窗口标题。

        layout = QGridLayout()
        layout.addWidget(label, 0, 0)
        layout.addWidget(self.spinBox, 0, 1)
        layout.addWidget(self.startButton, 0, 2)
        layout.addWidget(self.viewer, 1, 0, 1, 3)
        self.setLayout(layout)

        self.setWindowTitle(self.tr("Simple Threading Example"))

makePicture()主要完成个操作:1. 禁止修改界面;2. 重新生成一个新的pixmap;3. 开始工作 线程的绘制操作。

    def makePicture(self):

        self.spinBox.setReadOnly(True)
        self.startButton.setEnabled(False)

        pixmap = QPixmap(self.viewer.size())
        pixmap.fill(Qt.black)
        self.viewer.setPixmap(pixmap)

        self.thread.render(self.viewer.size(), self.spinBox.value())

我们用viewer的大小和五角星的数目作为参数传递给work线程的render函数进行绘制操作。 其中五角星的数目从滑块获取(spinBox.value())。

当work完成一个五角星的绘制时,会发射一个信号,调用addImage()槽。addImage()槽根据五角星的 所在位置和对应的pixmap在view中显示。然后更新窗口。

    def addImage(self, rect, image):

        pixmap = self.viewer.pixmap()
        painter = QPainter()

        painter.begin(pixmap)
        painter.drawImage(rect, image)
        painter.end()

        self.viewer.update(rect)

我们通过QPainter完成绘制操作。

updateUi()槽在work线程完成全部操作的时候被触发。同时恢复窗口按钮和滑块的状态。

    def updateUi(self):

        self.spinBox.setReadOnly(False)
        self.startButton.setEnabled(True)

前面我们已经知道怎么在一个窗口部件中使用work线程,下面是work的具体实现。

Worker线程

为了能在线程中更好的使用Qt的信号槽特性,我们使用PyQt中的线程来代替Python本身的线程机制。

class Worker(QThread):

    def __init__(self, parent = None):

        QThread.__init__(self, parent)
        self.exiting = False
        self.size = QSize(0, 0)
        self.stars = 0

在work线程中保存一些基本的绘制信息,并对它们进行初始化。其中exiting用于记录线程的 工作状态。

每个五角星都通过QPainterPath绘制:

        self.path = QPainterPath()
        angle = 2*math.pi/5
        self.outerRadius = 20
        self.innerRadius = 8
        self.path.moveTo(self.outerRadius, 0)
        for step in range(1, 6):
            self.path.lineTo(
                self.innerRadius * math.cos((step - 0.5) * angle),
                self.innerRadius * math.sin((step - 0.5) * angle)
                )
            self.path.lineTo(
                self.outerRadius * math.cos(step * angle),
                self.outerRadius * math.sin(step * angle)
                )
        self.path.closeSubpath()

当work线程对象在被销毁的时候,需要停止线程。在__del__函数中调用线程的wait()等待 线程的退出。

    def __del__(self):

        self.exiting = True
        self.wait()

在渲染五角星之前,我们先记录相关的绘制信息,然后开启线程。

    def render(self, size, stars):

        self.size = size
        self.stars = stars
        self.start()

start()方式用来启动线程,并且运行线程类中的run()方法。在这里我们重新实现了run()方法。 我们通过render()函数来代替直接调用run(),这样我们就可以通过render给线程传递相关信息。 run()方法定义如下:

    def run(self):

        # Note: This is never called directly. It is called by Qt once the
        # thread environment has been set up.

        random.seed()
        n = self.stars
        width = self.size.width()
        height = self.size.height()

前面已经保存的属性信息对应要绘制五角星的数目和对应的绘制区域。

在绘制每个五角星的时候,我们检测self.exiting的状态,这样可以确保在任何时刻都可以退出 线程。

        while not self.exiting and n > 0:

            image = QImage(self.outerRadius * 2, self.outerRadius * 2,
                           QImage.Format_ARGB32)
            image.fill(qRgba(0, 0, 0, 0))

            x = random.randrange(0, width)
            y = random.randrange(0, height)
            angle = random.randrange(0, 360)
            red = random.randrange(0, 256)
            green = random.randrange(0, 256)
            blue = random.randrange(0, 256)
            alpha = random.randrange(0, 256)

            painter = QPainter()
            painter.begin(image)
            painter.setRenderHint(QPainter.Antialiasing)
            painter.setPen(Qt.NoPen)
            painter.setBrush(QColor(red, green, blue, alpha))
            painter.translate(self.outerRadius, self.outerRadius)
            painter.rotate(angle)
            painter.drawPath(self.path)
            painter.end()

具体的绘制代码和多线程关系并不大,我们只是在区域内随机地点绘制不同的五角星。

当每个五角星被绘制完成时刻,我们通过发射"output(QRect, QImage)"信号来通知GUI线程 来执行相关的操作。

            self.emit(SIGNAL("output(QRect, QImage)"),
                      QRect(x - self.outerRadius, y - self.outerRadius,
                            self.outerRadius * 2, self.outerRadius * 2), image)
            n -= 1

用这种方式可以在不同线程之间传递QRect和QImage对象。

启动代码

if __name__ == "__main__":

    app = QApplication(sys.argv)
    window = Window()
    window.show()
    sys.exit(app.exec_())

全部代码

时间: 2024-10-05 20:49:28

多线程中的信号/槽的相关文章

no such signal-QT多线程信号槽没有响应

问题描述 QT多线程信号槽没有响应 程序逻辑很简单,就是让一个独立线程发送一个信号,UI线程的槽响应,但是运行报错居然是找不到信号 No such signal ,但是编译可以通过,moc文件也有这个信号.大侠们指点一下子,谢谢 #include "dialog.h" #include "ui_dialog.h" Dialog::Dialog(QWidget *parent) : QDialog(parent), ui(new Ui::Dialog) { ui-&g

PySide中的信号和槽

本文主要介绍在PySide中如何使用信号和槽.传统的使用方式已经在参考文档里给出,我们的重点是解释如何使用新的代码风格来操作信号.槽. PyQt中使用信号.槽的新代码风格是在PyQt v4.5中介绍的,这个风格的主要目的是为Python程序员们提供一个符合Python风格的方式. 传统方式:SINGAL和SLOT QtCore.SIGNAL和QtCore.SLOT是Python使用Qt信号.槽传送机制的接口.这就是我们所说的旧方式. 下面这个例子使用了QPushButton的点击信号,而连接方法

PySide“.NET研究”中的信号和槽

本文主要介绍在PySide中如何使用信号和槽.传统的使用方式已经在参考文档里给出,我们的重点是解释如何使用新的代码风格来操作信号.槽. PyQt中使用信号.槽的新代码风格是在PyQt v4.5中介绍的,这个风格的主要目的是为Python程序员们提供一个符合Python风格的方式. 传统方式:SINGAL和SLOT QtCore.SIGNAL和QtCore.SLOT是Python使用Qt信号.槽传送机制的接口.这就是我们所说的旧方式. 下面这个例子使用了QPushButton的点击信号,而连接方法

C# 如何让 多线程中每个线程间隔毫秒执行同一个方法?

问题描述 多线程的好处让效率提高很多倍,但是在某些情况下要求操作同一个方法的时候要求有间隔,这个间隔当然是毫秒级别的否则多线程的意义就体现不出来,本问题就是怎么让多线程中每个线程间隔毫秒执行同一个方法,测试代码如下:privatevoidbtnTest_Click(objectsender,EventArgse){TestManyThreadtest=newTestManyThread();test.Start();} 主要代码如下classTestManyThread{privatestati

信号槽库:sigslot.h和sigc++使用

用qt的知道,qt有方便简单的信号槽机制,但需要专门的qt工具处理. 如果想直接使信号槽就可以使用sigslot库,或者sigc++库,或者boost中的signals,这里介绍sigslot和sigc++库. sigslot.h:只有一个头文件,使用简单方便. sigc++:包含文件多,但功能更强大. sigslot库 官方地址 http://sigslot.sourceforge.net/ 在vs2013中使用 包含头文件 #include "sigslot.h" 1 1 改动:

PyQt5 笔记(05):信号/槽

  PyQt 的很多类都内置了信号和槽.下图是 Qt 官方文档对 QThread 类中包含的信号/槽的描述:       一.信号/槽 都是内置的 请看一个最简单的程序: 按钮点击后,窗口关闭                 代码: class Test(QDialog): def __init__(self, parent=None): super().__init__(parent) btn = QPushButton('关闭', self) btn.clicked.connect(self

(单例设计模式中)懒汉式与饿汉式在多线程中的不同

/*  目的:分析一下单例设计模式中,懒汉式与饿汉式在多线程中的不同!  开发时我们一般选择饿汉式,因为它简单明了,多线程中不会出现安全问题!  而饿汉式需要我们自己处理程序中存在的安全隐患,但是饿汉式的程序技术含量更高! */ /* class SinglePerson implements Runnable{    private static SinglePerson ss = new SinglePerson("hjz", 22);//恶汉式    private int ag

关于java多线程中的join方法

问题描述 关于java多线程中的join方法 1.主线程可能在子线程结束之前 结束吗?如果可能的话 举一个例子 2.如何理解join方法, 结合实际应用. 非常感谢非常感谢!!! 解决方案 关于join,参考:http://www.blogjava.net/jnbzwm/articles/330549.html 解决方案二: 主线程可能在子线程结束之前 结束吗 一般来说不可以,但是也不一定,如果子线程在执行finally中的代码,应该会等它执行完了才退出. 晕,join方法和什么"让主线程等子线

Java多线程中的两个问题

多线程|问题 多线程中Thread.stop()被废弃的原因:当调用Thread.stop()方法时,该线程将释放先前其控制的所有资源,而在线程没有正常执行完毕之前强迫Stop之后,这些资源可能处在一种不一致的状态,而这些处于不一致的状态的资源被其他的线程所使用之后,就可能会发生一些意想不到的错误.实现时间差事件的解决办法:在主线程中设置一个状态变量,在响应线程执行时,先sleep()一个固定的时间段,之后检查主线程的这个状态,如果这个状态不同就执行不同的操作,或停止执行.可以通过回调机制来实现