zoukankan      html  css  js  c++  java
  • 后台线程 与 信号

    界面阻塞问题

    前面我们的练习里开发了一个类似 Postman 的HTTP接口测试工具。

    其中,具体发送请求消息的代码如下

    def sendRequest(self):
    
            method = self.ui.boxMethod.currentText()
            url    = self.ui.editUrl.text()
            payload = self.ui.editBody.toPlainText()
    
            # 获取消息头
            headers = {}
            # 此处省略一些对消息头的处理
    
            req = requests.Request(method,
                                   url,
                                   headers=headers,
                                   data=payload
                                   )
    
            prepared = req.prepare()
    
            self.pretty_print_request(prepared)
            s = requests.Session()
            
            try:
                # 发送请求并且接收响应消息
                r = s.send(prepared)
                # 打印出响应消息
                self.pretty_print_response(r)
            except:
                self.ui.outputWindow.append(
                    traceback.format_exc())


    这里有一个问题:

    我们 点击发送按钮 发送HTTP消息消息,如果服务端接收处理的比较慢,就会导致下面这行代码中的send方法要比较长的时间才能返回。

    r = s.send(prepared)


    这会导致什么问题呢?

    假设10秒钟后,才接收到服务端的响应消息,这时候,界面就会 僵死 10秒钟。

    这期间,你点击界面没有任何反应。

    为什么呢?

    原因

    这是因为,我们现在的代码都是在主线程中执行的。

    其中最末尾的代码

    app.exec_()


    其实会让主线程进入一个死循环,循环不断的处理 用户操作的事件。

    当我们点击发送按钮后,Qt的 核心代码就会接受到这个 点击事件,并且调用相应的 slot函数去处理。

    因为我们代码做了这样的设置

    # 信号处理
            self.ui.buttonSend.clicked.connect(self.sendRequest)


    指定了点击发送按钮由 sendRequest 方法处理。

    如果这个sendRequest 很快能接收到 服务端的相应,那么sendRequest就可以很快的返回。

    返回后, 整个程序又进入到 app.exec_() 里面接收各种 事件,并且调用相应的函数去处理。界面就不会僵死,因为所有的操作界面的事件,都能得到及时的处理。

    但是,如果这个sendRequest 要很长时间才能返回,这段时间内,整个程序就停在 下面这行代码处

    r = s.send(prepared)


    自然就没有机会去处理其他的用户操作界面的事件了,当然程序就僵死了。

    子线程处理

    典型的一种解决方法就是使用多线程去处理。

    关于Python的多线程的讲解,可以点击参考我们这里的教程

    修改代码如下

    def sendRequest(self):
    
            method = self.ui.boxMethod.currentText()
            url    = self.ui.editUrl.text()
            payload = self.ui.editBody.toPlainText()
    
            # 获取消息头
            headers = {}
            # 此处省略一些对消息头的处理
    
            req = requests.Request(method,
                                   url,
                                   headers=headers,
                                   data=payload
                                   )
    
            prepared = req.prepare()
    
            self.pretty_print_request(prepared)
            s = requests.Session()
    
            # 创建新的线程去执行发送方法,
            # 服务器慢,只会在新线程中阻塞
            # 不影响主线程
            thread = Thread(target = self.threadSend,
                            args= (s, prepared)
                            )
            thread.start()
    
        # 新线程入口函数
        def threadSend(self,s,prepared):
    
            try:
                r = s.send(prepared)
                self.pretty_print_response(r)
            except:
                self.ui.outputWindow.append(
                    traceback.format_exc())


    这样,通过创建新的线程去执行发送方法,服务器响应再慢,也只会在新线程中阻塞

    主线程启动新线程后,就继续执行后面的代码,返回继续运行Qt的事件循环处理 ,可以响应用户的操作,就不会僵死了。

    子线程发信号更新界面

    点击这里,边看视频讲解,边学习下面的内容

    上面的示例中,我们在子线程里面操作了界面,如下代码所示

     def threadSend(self,s,prepared):
    
            try:
                r = s.send(prepared)
                # 在新线程中输出内容到界面
                self.pretty_print_response(r)
            except:
                # 在新线程中输出内容到界面
                self.ui.outputWindow.append(
                    traceback.format_exc())

    Qt建议: 只在主线程中操作界面

    在另外一个线程直接操作界面,可能会导致意想不到的问题,比如:输出显示不全,甚至程序崩溃。

    但是,我们确实经常需要在子线程中 更新界面。比如子线程是个爬虫,爬取到数据显示在界面上。

    怎么办呢?

    这时,推荐的方法是使用信号。

    前面我们曾经看到过 各种 Qt 控件可以发出信号,比如 被点击、被输入等。

    我们也可以自定义类,只要这个类继承QObject类,就能发出自己定义的各种Qt信号,具体做法如下:

    • 自定义一个Qt 的 QObject类,里面封装一些自定义的 Signal信号

      怎么封装自定义的 Signal信号?参考下面的示例代码。

      一种信号定义为 该类的 一个 静态属性,值为Signal 实例对象即可。

      可以定义 多个 Signal静态属性,对应这种类型的对象可以发出的 多种 信号。

      注意:Signal实例对象的初始化参数指定的类型,就是 发出信号对象时,传递的参数数据类型。因为Qt底层是C++开发的,必须指定类型。

    • 定义主线程执行的函数处理Signal信号(通过connect方法)

    • 在新线程需要操作界面的时候,就通过自定义对象 发出 信号

      通过该信号对象的 emit方法发出信号, emit方法的参数 传递必要的数据。参数类型 遵循 定义Signal时,指定的类型。

    • 主线程信号处理函数,被触发执行,获取Signal里面的参数,执行必要的更新界面操作

    一个示例代码如下

    from PySide2.QtWidgets import QApplication, QTextBrowser
    from PySide2.QtUiTools import QUiLoader
    from threading import Thread
    
    from PySide2.QtCore import Signal,QObject
    
    # 自定义信号源对象类型,一定要继承自 QObject
    class MySignals(QObject):
    
        # 定义一种信号,两个参数 类型分别是: QTextBrowser 和 字符串
        # 调用 emit方法 发信号时,传入参数 必须是这里指定的 参数类型
        text_print = Signal(QTextBrowser,str)
    
        # 还可以定义其他种类的信号
        update_table = Signal(str)
    
    # 实例化
    global_ms = MySignals()    
    
    class Stats:
    
        def __init__(self):
            self.ui = QUiLoader().load('main.ui')
    
            # 自定义信号的处理函数
            global_ms.text_print.connect(self.printToGui)
    
    
        def printToGui(self,fb,text):
            fb.append(str(text))
            fb.ensureCursorVisible()
    
        def task1(self):
            def threadFunc():
                # 通过Signal 的 emit 触发执行 主线程里面的处理函数
                # emit参数和定义Signal的数量、类型必须一致
                global_ms.text_print.emit(self.ui.infoBox1, '输出内容')
            
            thread = Thread(target = threadFunc )
            thread.start()
    
        def task2(self):
            def threadFunc():
                global_ms.text_print.emit(self.ui.infoBox2, '输出内容')
    
            thread = Thread(target=threadFunc)
            thread.start()
    天道酬勤 循序渐进 技压群雄
  • 相关阅读:
    【阿里聚安全·安全周刊】苹果证实 iOS 源代码泄露|英国黑客赢下官司
    150万元重奖!阿里软件供应链安全大赛正式启动
    【阿里聚安全·安全周刊】山寨外挂有风险养蛙需谨慎|健身追踪热度图爆军事基地位置
    移动APP外挂攻防实战
    阿里云正式上线移动直播问答解决方案,助力APP尽情“撒币”!
    阿里安全资深专家杭特辣评中国网络安全人才之“怪现状”
    【阿里聚安全·安全周刊】Intel芯片级安全漏洞事件|macOS存在漏洞
    阿里聚安全年终盘点|2017互联网安全领域十大话题
    【技术分析】DowginCw病毒家族解析
    独家探寻阿里安全潘多拉实验室,完美越狱苹果iOS11.2.1
  • 原文地址:https://www.cnblogs.com/wuyuan2011woaini/p/14755542.html
Copyright © 2011-2022 走看看