【问题标题】:PyQt5 application slow GUI response for unknown reasonsPyQt5 应用程序由于未知原因导致缓慢的 GUI 响应
【发布时间】:2020-05-12 15:34:24
【问题描述】:

我正在创建一个带有套接字的 PyQt5 应用程序。为了防止套接字阻塞 GUI 更新,我将套接字外包给了它自己的线程。但是,我仍然遇到一些 GUI 冻结或更新缓慢的问题。它通常在套接字接收数据并将其传递给 GUI 时恢复,我很难弄清楚在哪里。当调整或移动主窗口或其子窗口时,该问题尤其出现。

我已将以下代码简化为基础:创建一个 GUI,启动套接字线程,从套接字读取数据,对其进行处理,并在其控制台缓冲区中显示结果。另外,用控制台输入做一些事情(将结果传递给套接字还没有实现)。

数据通过inQueueoutQueue 在GUI 和套接字之间传递,并且GUI 应该从触发updateGui() 函数的QTimer() 更新。我现在将其设置为 500,将其增加到 5 秒(5000)似乎可以减少 GUI 的故障,但并非始终如一。理想情况下,我想将其降低到 100 毫秒甚至更低,以获得更灵敏的 GUI,但现在我将其设置为 500 以用于调试目的。

import os
import sys
import socket
import time

from PyQt5.QtWidgets import QApplication, QMainWindow, QMdiArea
from PyQt5.Qt import QWidget, QMdiSubWindow, QVBoxLayout, QGridLayout, QHBoxLayout, QTextEdit, QLineEdit, QFormLayout, QObject
from PyQt5.QtCore import QTimer
import threading
from threading import Thread
from queue import Queue

class Gui(QMainWindow):
    def __init__(self):

        # Set up GUI with a sub-window named `consoleWindow` from the `ConsoleWindow()` class
        super(Gui, self).__init__()
        self.mainWin = QWidget()
        self.mainWin.resize(640, 480)
        self.winArea = QMdiArea(self.mainWin)
        self.mainLayout = QVBoxLayout()
        self.mainLayout.setStretch(1, 1)
        self.mainLayout.addWidget(self.winArea)
        self.mainWin.setLayout(self.mainLayout)

        # set up consoleWindow
        self.consoleWindow = ConsoleWindow(self)
        self.winArea.addSubWindow(self.consoleWindow)
        self.mainWin.show()

        # Set up message queues for passing data between socketthread and GUI
        self.outQueue = Queue()
        self.inQueue = Queue()

        # Set up and launch socketthread
        self.socket = SocketWorker(self, SETTINGS_HOST, SETTINGS_PORT, self.inQueue, self.outQueue)
        self.socketThread = threading.Thread(target=self.socket.createAndRun, args=(self.inQueue, self.outQueue))
        self.socketThread.start()

        # Periodically do stuff
        self.timer = QTimer(self)
        self.timer.setInterval(500)
        self.timer.timeout.connect(self.updateGui)
        self.timer.start()

    def consoleInput(self, input):
        # TODO do stuff with input. Not a whole lot happening here yet.

    def updateGui(self):
        if self.inQueue.not_empty:
            self.consoleWindow.append(self.inQueue.get())

class ConsoleWindow(QMdiSubWindow):
    def __init__(self, gui):

        # Setup console
        super(ConsoleWindow, self).__init__()
        self.gui = gui
        self.consoleContents = QWidget()
        self.buffer = QTextEdit()
        self.buffer.setReadOnly(True)
        self.input = QLineEdit()
        self.input.returnPressed.connect(self.cmdinput)

        # Create console widgets
        self.consoleLayout = QVBoxLayout(self.consoleContents)
        self.consoleLayout.addWidget(self.buffer)
        self.consoleLayout.addWidget(self.input)

        self.consoleContents.setLayout(self.consoleLayout)
        self.setWidget(self.consoleContents)

    def append(self, string):
        self.buffer.append(string)

    def cmdinput(self):
        self.gui.consoleInput(self.input.text())
        self.input.clear()

class SocketWorker(Thread):
    def __init__(self, gui, host, port, inQueue, outQueue):
        Thread.__init__(self)
        self.gui = gui
        super(SocketWorker, self).__init__()

    def createAndRun(self, inQueue, outQueue):
        self.inQueue = inQueue
        self.outQueue = outQueue

        # SNIP: Create socket, continuously read from it, process incoming data, and shove the results into inQueue. 
        # SNIP: Process any data in outQueue as produced by the GUI, and shove through the socket.

理论:

  • 我在自己的线程中启动 SocketWorker 失败了吗?
  • GUI 设置是否效率极低?
  • 排队有什么问题?

更新 1:

我发现如果我在def updateGui() 中注释掉self.consoleWindow.append(self.inQueue.get()),问题就会消失,表明Queue() 有问题

更新 2:

我想通了。请参阅下面的答案。

【问题讨论】:

  • 能否在代码中也包含所需的imports?
  • @Asocia 添加。不过,那里可能有一些未使用的导入。
  • @Jarmund 请提供minimal reproducible example

标签: python python-3.x multithreading pyqt5


【解决方案1】:

事实证明queue.Queue() 有一个超时,因此导致了锁,因为它经常被轮询。解决方法是改用get_nowait()。但是,这导致该方法出现异常raise Empty。所以我做了一个懒惰的修复,将它包装在一个 try:except: 块中,这就成功了。它现在可以工作了,我已经将 QTimer 减少到 10 毫秒,没有任何问题。该函数现在看起来像这样:

def updateGui(self):
    if self.inQueue.not_empty:
        try:
            self.consoleWindow.append(self.inQueue.get_nowait())
        except:
            True

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-08-01
    • 1970-01-01
    • 2013-04-09
    • 1970-01-01
    • 2019-03-05
    • 1970-01-01
    • 2012-04-03
    相关资源
    最近更新 更多