PyQt多线程使用

最近在做一个系统。对代码进行主线程和子线程的分离,将比较耗时的数据处理操作放在子线程里面操作,防止卡死。由于最后系统使用的环境很杂,pyqt5版本过高,在一些xp的低版本操作系统是运行会出问题,所以使用的pyqt4,但原理一样。
原理参考博文最后链接,这里记录自己的使用方法

比如,在通过实现制作TFrecords文件,并记录文件照片数量。这里有循环读取的耗时操作。利用多线程实现如下:

步骤:

1.创建线程类

from PyQt4 import QtCore
from PyQt4.QtCore import pyqtSignal


class WorkThread(QtCore.QThread):#定义一个工作线程,后面会调用和重写
    # 使用信号和UI主线程通讯,参数是发送信号时附带参数的数据类型,可以是str、int、list等
    finishSignal = pyqtSignal(int)

    def run(self):#线程启动后会自动执行,这里是逻辑实现的代码
        self.finishSignal.emit(int) #发射信号

WorkThread是创建的一个线程类,后面会重写他的run方法
实现制作数据集的操作:

class dealThread(WorkThread): #1.制作数据集
    finishSignal = pyqtSignal(int,int)

    # 带3个参数
    def __init__(self,dataset_param,total_label,tf_record):
        super(dealThread, self).__init__()

        self.datasetparam_ui = dataset_param
        self.totalnum_label = total_label
        self.Tfrecord_ui = tf_record

    #处理逻辑业务
    def run(self):
        try:
            if self.datasetparam_ui.tfrecordstart_radioButton.isChecked():# Tfrecord文件
                saveTfrecord_path = self.datasetparam_ui.datasetsavepath_label.text()  # 保存路径
                ftrecordfilename = ("train.tfrecords")  # tfrecords格式文件名

                # 通过这一句将数据写入到TFRecord文件,参数为TFrecord的文件路径,入口
                writer = tf.io.TFRecordWriter(saveTfrecord_path + "\\" + ftrecordfilename)

                # 标签
                with open(self.datasetparam_ui.csvlabel_label.text(), 'r') as ff:
                    reader = csv.reader(ff)
                    images_list = []
                    labels = []
                    for row in reader:
                        images_list.append(row[0])
                        labels.append(row[1])
                ff.close()

                lines = 1
                bad = 0
                contents = self.get_file(self.datasetparam_ui.datasetfile_label.text(), [])
                line_total = len(contents)
                # print("总数:",line_total)

                for filename in contents:
                    lines += 1
                    img_ok = imghdr.what(filename)  # 判断图片是否损坏
                    if img_ok is None:
                        bad += 1
                        continue

                    path_pic = os.path.abspath(filename)[:-12]
                    # print(path_pic)
                    Cam_name = os.path.abspath(path_pic).split('\\')[-1]  # 相机名称
                    Cam_name = Cam_name + "\t"  # 以0开头的以文本形式保留
                    # print(Cam_name)

                    img = Image.open(filename, "r")
                    exif_data = img._getexif()
                    date_time = exif_data.setdefault(306)  # 得到日期时间格式,如2016:03:24 13:07:36
                    img_date = date_time.replace(":", "/", 2)
                    # print(img_date)

                    width = int(self.Tfrecord_ui.tfpicwidth_lineEdit.text())
                    height = int(self.Tfrecord_ui.tfpicheight_lineEdit.text())
                    img = img.resize((width, height))
                    img_raw = img.tobytes()  # 将图片转化为二进制格式

                    label = 2  # 原始标记
                    for im_id in range(1,len(images_list)):
                        if filename[-23:] == images_list[im_id][-23:]:
                            label = int(labels[im_id])
                            # print(label)
                            break
                        else:
                            continue

                    # 通过该方法实现格式存储,非常重要
                    example = tf.train.Example(
                        # 初始化Features对象,一般传入一个字典,字典的键是一个字符串,表示名字,字典的值是一个tf.train.Feature对象.
                        features=tf.train.Features(feature={
                            'path': tf.train.Feature(bytes_list=tf.train.BytesList(value=[img_raw])),
                            'name': tf.train.Feature(
                                bytes_list=tf.train.BytesList(value=[bytes(filename[-12:], encoding='utf8')])),
                            'Cam': tf.train.Feature(
                                bytes_list=tf.train.BytesList(value=[bytes(Cam_name, encoding='utf8')])),
                            'YY': tf.train.Feature(int64_list=tf.train.Int64List(value=[int(img_date[-19:-15])])),
                            'MM': tf.train.Feature(int64_list=tf.train.Int64List(value=[int(img_date[-14:-12])])),
                            'DD': tf.train.Feature(int64_list=tf.train.Int64List(value=[int(img_date[-11:-9])])),
                            'HH': tf.train.Feature(int64_list=tf.train.Int64List(value=[int(img_date[-9:-6])])),
                            'mm': tf.train.Feature(int64_list=tf.train.Int64List(value=[int(img_date[-5:-3])])),
                            'SS': tf.train.Feature(int64_list=tf.train.Int64List(value=[int(img_date[-2:])])),
                            'date': tf.train.Feature(
                                bytes_list=tf.train.BytesList(value=[bytes(img_date[-19:-9], encoding='utf8')])),
                            'label': tf.train.Feature(int64_list=tf.train.Int64List(value=[label])),
                        }))
                    writer.write(example.SerializeToString())
                    self.finishSignal.emit(lines, line_total)  # 发射信号,捕获后显示

                writer.close()
                self.totalnum_label.setText(str(lines - bad))  # 显示数据总数
        except:
            pass

代码解释:

(1)定义了一个信号:finishSignal = pyqtSignal(int,int),包含两个参数,这两个参数需要发射出去,用于新窗口的数据显示等操作。
(2)def init(self,dataset_param,total_label,tf_record):
这里传入了新的参数,这些参数会在run方法里面用到,所以需要外部传入。dataset_param表示的是一个窗口,用于创建数据集的参数设置,total_label表示主窗口的显示标签。tf_record表示tfrecord文件的图片大小设置的参数,即宽和高。
PyQt多线程使用_第1张图片
PyQt多线程使用_第2张图片
PyQt多线程使用_第3张图片
最后还有加上:
self.finishSignal.emit(lines, line_total)
将信号发射出去,其中包含了run方法执行后得到的变量值,类型是开始定义的信号类型。需要一一对应。

2. 信号接收与多线程。

明确信号控制的操作 也就是槽函数。信号发出之后希望进行的操作,通过信号所在类实例.信号名.connect(槽函数)实现。

    # 1.创建数据集
    def makeFilecsv_TFrecord(self):
        try:
            self.make_thread = dealThread(self.datasetparam_ui, self.totalnum_label, self.Tfrecord_ui)
        except:
            self.make_thread = dealThread(self.datasetparam_ui, self.totalnum_label, [])

        self.make_thread.start()  # 执行run函数
        self.make_thread.finishSignal.connect(self.show_data_makedataset)
    #   线程执行后显示
    def show_data_makedataset(self,lines,line_total):
        #数据写入后显示进度
        if self.datasetparam_ui.csvstart_radioButton.isChecked():
            self.label.setText("正在制作.csv格式数据集...")

        if self.datasetparam_ui.tfrecordstart_radioButton.isChecked():
            self.label.setText("正在制作TFRecords格式的数据集...")

        self.totalnum_label.setText(str(lines))  # 显示数据总数
        self.main_progressBar.setValue(lines * (100 / (line_total)))

        if self.main_progressBar.value() ==100:
            self.label.setText("数据集制作完成!")
            QtGui.QMessageBox.about(self, '提示', "数据集制作完成!")

代码解释:

(1)makeFilecsv_TFrecord函数里面需要对创建的线程类进行实例化,即:

 self.make_thread = dealThread(self.datasetparam_ui, self.totalnum_label, self.Tfrecord_ui)

其中,需要传入参数,也就是前面说到的那几个窗体的内容。因为这些数据是外部传入的,可以理解为在平常做开发的时候,把run里面的代码直接粘贴到这个函数里面就可以执行了,但是为了避免执行过程中较长的处理时间而出现主线程界面的卡顿(界面无响应),所以我们把这部分数据的处理放在了run方法里面,但是run方法里面的代码,有些参数就需要外部传递过去,才可以实现。
(2)我这里用了异常处理是因为我还进行了csv文件的制作。制作csv文件时,没有tfrecord的事情,所以传入了一个空值。

(3)绑定自己定义的信号,代码如下,这部分表示run方法执行完成后,需要做什么操作,这里我们可以通过connect来绑定槽函数,该槽函数就可以是我们用来显示的页面了,这样就把处理和显示分开了。

self.make_thread.finishSignal.connect(self.show_data_makedataset)

其中槽函数show_data_makedataset()表示的就是需要显示的数据内容,由于该数据是emit发射出来的,这里需要接收参数用于显示,所以带了几个参数。
(4)self.make_thread.start() # 执行run函数

3.执行

self.datasetstart_action.triggered.connect(self.makeFilecsv_TFrecord)  # 创建数据集

点击创建执行makeFilecsv_TFrecord函数,即多线程触发。

4.效果

PyQt多线程使用_第4张图片

PyQt多线程使用_第5张图片

上图的49就表示run执行后发射回来用于显示的值。由于是会动态的变化,所以把它单独分离处理。这样就不会出现页面无响应这样的问题。

注:run函数内的代码是对tfrecord文件的制作,具体制作过程可以参考其他博客。

参考链接1

参考链接2

你可能感兴趣的:(PyQt,tensorflow)