使用QThreading和QProcess进行GUI冻结

时间:2020-05-21 04:23:47

标签: python pyqt5 python-multithreading qthread 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循环生成了多个QProcess导致的,但是我不确定。

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

1 个答案:

答案 0 :(得分:3)

说明

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

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

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

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

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

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

  • “ runTest”是具有局部作用域的对象,将在startTestThread方法完成执行后立即销毁,因此根据(3),run方法将在QThread所属的线程中执行,并根据(4),这将是GUI。

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

解决方案

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

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()
相关问题