【问题标题】:GUI Freezing with QThreading and QProcess使用 QThreading 和 QProcess 冻结 GUI
【发布时间】:2020-05-21 04:23:47
【问题描述】:

我正在尝试编写一些软件来处理从一些晶体学实验中收集的大量图像。数据处理包括以下步骤:

  1. 用户输入来确定要一起批处理的图像数量。
  2. 选择包含图像的目录,并计算图像总数。
  3. 嵌套的 for 循环用于将图像批处理在一起,并为使用批处理文件处理的每个批处理构造命令和参数。

下面的代码可以用来模拟使用QThread和QProcess描述的过程:

# This Python file uses the following encoding: utf-8
import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
import test
import time

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.ui=test.Ui_test()
        self.ui.setupUi(self)
        self.ui.pushButton_startThread.clicked.connect(self.startTestThread)

    def startTestThread(self):
        self.xValue = self.ui.lineEdit_x.text() #Represents number of batches
        self.yValue = self.ui.lineEdit_y.text() #Represents number of images per batch
        runTest = testThread(self.xValue, self.yValue) #Creates an instance of testThread
        runTest.start() #Starts the instance of testThread

class testThread(QThread):
    def __init__(self, xValue, yValue):
        super().__init__()
        self.xValue = xValue
        self.yValue = yValue

    def __del__(self):
        self.wait()

    def run(self):
        for x in range(int(self.xValue)): #For loop to iterate througeach batch
            print(str(x) + "\n")
            for y in range(int(self.yValue)): #For loop to iterate through each image in each batch
                print(str(y) + "\n")
            print(y)
            process = QProcess(self) #Creates an instance of Qprocess
            process.startDetached("test.bat") #Runs test.bat

    def stop(self):
        self.terminate()


if __name__ == "__main__":
    app = QApplication(sys.argv)
    app.setStyle("Fusion")
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())

test.bat 内容:

@ECHO OFF
ECHO this is a test

GUI 包含两个用于 xValue 和 yValue 的用户输入以及一个用于启动线程的按钮。例如,一项实验产生 150,000 张图像,需要分批处理 500 张图像。这将需要每批处理 300 张图像。您可以为 xValue 输入 500,为 yValue 输入 300。有两个问题:

  1. GUI 冻结,因此如果需要,我无法停止进程。我认为运行线程应该可以防止这种情况发生。
  2. 我收到以下错误:
QObject: Cannot create children for a parent that is in a different thread.
(Parent is testThread(0x1a413f3c690), parent's thread is QThread(0x1a4116cb7a0), current thread is testThread(0x1a413f3c690)

我相信这个错误是通过嵌套 for 循环生成多个 QProcesses 的结果,但我不完全确定。

是否有办法阻止 GUI 冻结并避免生成的错误?

【问题讨论】:

  • QProcess(self) 可能会导致错误消息。 run()在后台线程中调用,而self在主线程中引用testThread
  • 哦!谢谢!这就说得通了。修复了 QObject 错误!

标签: python pyqt5 python-multithreading qthread qprocess


【解决方案1】:

解释

要了解问题的原因,必须清楚以下概念:

  1. 一个QThread不是一个Qt线程,也就是说它不是一个Qt创建的线程,而是每个操作系统的原生线程的一个QObject handler。

  2. 只有 QThread 的 run() 方法中的内容才会在另一个线程中执行。

  3. 如果 QThread 被销毁,那么 run() 方法将不会在辅助线程上执行,而是在 QThread 所属的线程上执行。

  4. QObject 与父级属于同一线程,如果没有父级,则属于创建它的线程。

考虑到上述情况,这两个错误都可以解释:

  • “runTest”是一个局部作用域的对象,在startTestThread方法执行完毕后会立即销毁,所以根据(3)run方法会在QThread所属的线程中执行,根据 (4) 这将是 GUI。

  • 考虑到 (4) 显然 QProcess 属于主线程(因为它的父级是 QThread 并且它属于主线程),但是您在辅助线程 (2) 中创建它可能会导致问题,所以Qt 会对此发出警告。

解决方案

对于第一个问题,只需延长它的生命周期,例如通过将其传递给父级(或使其成为类的属性)。对于第二个问题,不需要创建 QProcess 的实例,因为您可以使用静态方法 (QProcess::startDetached())。考虑到这一点,解决方案是:

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.ui=test.Ui_test()
        self.ui.setupUi(self)
        self.ui.pushButton_startThread.clicked.connect(self.startTestThread)

    def startTestThread(self):
        self.xValue = self.ui.lineEdit_x.text() #Represents number of batches
        self.yValue = self.ui.lineEdit_y.text() #Represents number of images per batch
        runTest = testThread(
            self.xValue, self.yValue, self
        )  # Creates an instance of testThread
        runTest.start()  # Starts the instance of testThread


class testThread(QThread):
    def __init__(self, xValue, yValue, parent=None):
        super().__init__(parent)
        self.xValue = xValue
        self.yValue = yValue

    def __del__(self):
        self.wait()

    def run(self):
        for x in range(int(self.xValue)):  # For loop to iterate througeach batch
            print(str(x) + "\n")
            for y in range(
                int(self.yValue)
            ):  # For loop to iterate through each image in each batch
                print(str(y) + "\n")
            print(y)
            QProcess.startDetached("test.bat")  # Runs test.bat

    def stop(self):
        self.terminate()

【讨论】:

  • 好的。所有这些都是有道理的!感谢您的答复;我学到了很多!我将它应用到我的程序中,它就像一个魅力!除了您的解决方案之外,我还发现了另一个我认为可以做同样事情的解决方案。我没有创建 QThread 类,而是创建了 QRunnable 类。在GUI类中,我创建了一个QThreadPool的实例,并通过QThreadPool的start()方法传递了QRunnable类。他们似乎需要相同的时间来完成相同的任务。使用 QRunnable 比使用 QThread 有什么优势吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-10-11
  • 1970-01-01
  • 2016-02-13
相关资源
最近更新 更多