PyQt5多线程编程

单位的公共仪器需要预约才能够使用,但是自从预约时间改到了早上,就很不好约了,所以决定写一个程序实现自动预约。由于还要分享给实验室其它同学使用,就得写一个GUI了,这一下代码量瞬间变成了原来的10倍,不过学到了些新东西,这波不亏。

在图形化编程中一个很重要的一点就是使用多线程,将UI线程独立出来。如果你的程序不存在会造成线程阻塞的操作,不使用多线程倒也没什么大问题,但是如果存在像联网、读写文件等可能需要等待的操作,使用单线程就可能会造成UI假死,这对于用户体验来说是非常不友好的。例如微软的office套件为了兼容老式CPU,IO等操作都是使用的UI的线程,打开或保存大文件时,用户界面无响应;而像photoshop这样的程序由于使用了多线程,即使有大量的IO行为,UI也可以响应用户的操作。

Qt引以为豪的对象通讯机制被称为信号-槽(signal-slot)机制。当特定事件被触发时(如子线程结束)将发送一个信号,而与该信号建立的连接槽,则可以接收到该信号并做出反应(根据子线程的返回值执行操作)。

在我的程序中,需要在仪器开放预约前不断查询,在开放预约的第一刻立即提交表单,而为了不拖垮服务器,需要在每次查询后让线程休眠一会。如果所有这些操作都在主线程中完成,那么线程休眠的时候UI就会无响应,在我的程序中就表现为点击开始查询后UI就一直处于无响应状态,直到预约成功后才恢复响应。

因此,就需要新建一个线程,这个线程只用来发送信号调用查询函数,发送完毕后即休眠,等待下一次发送或任务完成后结束线程。

PyQt5多线程编程_第1张图片
灵魂画手的流程图

要创建一个新线程,需要在程序中定义一个类,这个类要继承QtCore.QThread,然后把要执行的操作放进run()函数中,线程启动后,就会自动运行这个函数。结束线程时通过调用线程的terminate()方法来中止线程:

class QueryWaitThread(QtCore.QThread):
    # 定义线程需要用到的信号
    query_signal = QtCore.pyqtSignal()      # 查询信号
    on_complete = QtCore.pyqtSignal(int)    # 每次查询完成时发送,可以发送参数,
                                                此处参数int为查询次数
    terminate_thread = QtCore.pyqtSignal()  # 结束线程信号

    def __init__(self):
        super(QueryWaitThread, self).__init__()

    def run(self): # 只发送信号,然后休眠。
        i = 1
        while True:
            self.on_complete.emit(i)        # 调用信号的emit()方法
            i += 1
            self.query_signal.emit()
            sleep(60)

    def kill_thread(self):
        self.terminate()

我的查询函数与验证函数:

def auto_query(self):
    ################
    #    查询操作   #
    ################
    if self.good_to_submit():
        self.btnstop.hide()
        self.btn_resel.show()
        main.Appoint(self)

def good_to_submit(self):
    if # 还无法提交表单:
        return False
    self.T.terminate_thread.emit() # 验证可以提交表单后发送中止信号
    return True

def print_current_count(self, i):
    self.normalOutputWritten("当前为第 %i 次查询...\n" % i)

self.normalOutputWritten()是一个我自己定义的函数,用于在窗体的文本框中打印信息,你也可以定义自己的这类函数。

定义好信号以后,接下来需要调用信号的connect()方法将信号连接到需要执行函数上。下面的start()stop()是窗体按钮按下后执行的操作,我们在这两个函数中使用刚刚定义好的信号:

def start(self):
    ################
    # do something #
    ################
    self.T = QueryWaitThread()
    self.T.query_signal.connect(self.auto_query)
    self.T.on_complete.connect(self.print_current_count)
    self.T.terminate_thread.connect(self.T.kill_thread)
    self.normalOutputWritten("已开启自动查询,当有符合条件的时间段时将自动提交预约...\n")
    self.T.start()  # 开始执行线程函数
    self.btnstop.show()
    self.btn_resel.hide()

def stop(self):
    self.btnstop.hide()
    self.btn_resel.show()
    self.T.terminate_thread.emit() # 发送中止信号
    self.normalOutputWritten("用户终止查询。\n")

当按下开始按钮后,程序会构造出一个线程T,执行到T.start()后,其中的run()函数便会进入while循环,不断发送查询信号,直到good_to_submit()函数返回True表明已经可以提交表单,或者当停止按钮按下后终止。

改进后的程序只是将定时功能分离了出来,实际上联网查询功能还是使用主线程完成的,在程序等待服务器返回数据的过程中UI还是会有短暂的无响应时间,不过这是在用户点击后产生的,因此对体验影响不大。但是,严格来说联网也应该分离出一个线程,毕竟服务器有时候返回会很慢,这时候如果你的用户是一个暴躁老哥,还是会对体验有影响的。

你可能感兴趣的:(PyQt5多线程编程)