使用 QThreading 和 QProcess 冻结 GUI

GUI Freezing with QThreading and QProcess

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

  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 冻结并避免产生错误?

解释

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

  1. QThread 不是 Qt 线程,也就是说它不是 Qt 创建的线程,而是本机线程的 QObject 处理程序每个 OS.

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

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

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

综合以上,两个错误都可以解释:

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

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

解决方案

对于第一个问题,简单地延长它的生命周期,例如通过传递给它一个父对象(或者使它成为class的一个属性)。对于第二个问题,没有必要创建 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, <b>self</b>
        )  # Creates an instance of testThread
        runTest.start()  # Starts the instance of testThread


class testThread(QThread):
    def __init__(self, xValue, yValue, <b>parent=None</b>):
        super().__init__(<b>parent</b>)
        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)
            <b>QProcess.startDetached("test.bat")</b>  # Runs test.bat

    def stop(self):
        self.terminate()