美文网首页
PyQt5多线程编程

PyQt5多线程编程

作者: 碳负离子 | 来源:发表于2019-08-04 17:43 被阅读0次

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

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

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

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

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

    灵魂画手的流程图

    要创建一个新线程,需要在程序中定义一个类,这个类要继承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多线程编程

          本文链接:https://www.haomeiwen.com/subject/gypydctx.html