GUI Freezing with QThreading and QProcess

回眸只為那壹抹淺笑 提交于 2021-02-08 07:39:40

问题


I'm attempting to write some software that will process large amounts of images collected from some crystallography experiments. The data process involves the following steps:

  1. User Input to determine the number of images to batch together.
  2. A directory containing the images is selected, and the total number of images is calculated.
  3. A nested for loop is used to batch images together, and construct a command and arguments for each batch which is processed using a batch file.

The following code can be used to simulate the process described using QThread and 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 contents:

@ECHO OFF
ECHO this is a test

The GUI contains two user inputs for xValue and yValue and a button to start a thread. For example, one experiment yields 150,000 images that need to be processed in batches of 500. This will require 300 images per batch to be processed. You can enter 500 for xValue and 300 for yValue. There are two problems:

  1. The GUI freezes, so I can't stop the process if I need to. I thought running a thread was supposed to prevent this.
  2. I receive the following error:
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)

I believe this error is a result of the multiple QProcesses being generated through the nested for loop, but I'm not entirely sure.

Is there anyway to stop the GUI from freezing and avoid the generated error?


回答1:


Explanation

To understand the cause of the problem then the following concepts must be clear:

  1. A QThread is not a Qt thread, that is to say it is not a thread created by Qt but it is a QObject handler of the native threads of each OS.

  2. Only what is inside QThread's run() method is executed in another thread.

  3. If a QThread is destroyed then the run() method will not be executed on a secondary thread but on the thread to which the QThread belongs.

  4. A QObject belongs to the same thread as the parent, and if it has no parent then it belongs to the thread where it was created.

Considering the above, both errors can be explained:

  • "runTest" is an object with local scope that will be destroyed an instant after the startTestThread method is finished executing, so according to (3) the run method will be executed in the thread to which the QThread belongs, and according to (4) this will be the the GUI.

  • Considering (4) clearly the QProcess belongs to the main thread (since its parent is QThread and it belongs to the main thread), but you are creating it in a secondary thread (2) that can cause problems so Qt warns you of this.

Solution

For the first problem, simply extend its life cycle, for example by passing it a parent (or make it an attribute of the class). For the second problem it is not necessary to create an instance of QProcess since you can use the static method(QProcess::startDetached()). Considering this, the solution is:

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()


来源:https://stackoverflow.com/questions/61927243/gui-freezing-with-qthreading-and-qprocess

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!