JoshG
JoshG

Reputation: 13

PyQt Threading: GUI interaction Crashes Program

I am designing a program to edit DICOMs. Specifically, I am having issues appropriately interacting with my PyQt UI.

I want to be able to click on a "pause" and on a "stop" button to either pause or stop my editing function. My editing function takes a significant amount of time to process / loop through. Depending on the number of files that it is editing, it can take anywhere from 30 seconds to over an hour. Because of this, I decided to throw my editing function into its own thread using the native threading capabilities of Qt. I was able to get the thread working ie: from my MainWindow class I can click a button that initializes my editing class (class edit(QThread), however interacting with the GUI still crashes the program and I'm not sure why! Below I have added a sample of the general code structure / set up that I am using.

class anonymizeThread(QThread):
    def __init__(self):
        QThread.__init__(self)


    def __del__(self):
        self.wait()

    #def sendAnon(self, progress_val):
     #   self.completed = 0
      #  return self.completed


    def run(self):
            # while self.completed < 100:
            #     self.completed += 0.00001
            #     self.emit(QtCore.SIGNAL('PROGRESS'), self.completed)
            # ANONYMIZE FUNCTION!
            i = 0
            #flag = self.stop_flag
            while i < 10000000: # and self.stop_flag is not 1:
                print(i)
                i+=1
            print('i didnt enter the loop')


class MainWindow(QMainWindow, Ui_MainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.setupUi(self)

        # connect the buttons
        self.worker = anonymizeThread()
        self.anonbtn.clicked.connect(self.anonymize)
        self.open_directory.clicked.connect(self.open_dir)
        self.pause.clicked.connect(self.paused)
        self.stopbtn.clicked.connect(self.stopped)

        # block button signals to start
        self.pause.blockSignals(True)
        self.stopbtn.blockSignals(True)

        self.dir_name = None
        self.pause_flag = None
        self.stop_flag = None
        self.anon_flag = None

        # This is how we quit from the main menu "File" option
        extractAction = self.actionQuit_Ctrl_Q
        extractAction.setShortcut("Ctrl+Q")
        extractAction.setStatusTip('Leave The App')
        extractAction.triggered.connect(self.close_application)



    def updateProgressBar(self,val):
        self.progressBar.setValue(val)
    def close_application(self):
        choice = QMessageBox.question(self, 'Just had to check...', "Are you sure you want to exit?", QMessageBox.Yes | QMessageBox.No)
        if choice == QMessageBox.Yes:
            sys.exit()
        else:
            pass

    def anonymize(self):
        self.pause.blockSignals(False)
        self.stopbtn.blockSignals(False)
        self.worker.start()
        # check if directory chosen
        # self.progressBar.setMaximum(len(dcm)
        # start our anon thread!


    def paused(self):
        #only if running
        if self.pause_flag is 0:
            self.pause_flag = 1
            self.pause.setText('Start')
        elif self.pause_flag is 1:
            self.pause_flag = 0
            self.pause.setText('Pause')
        else:
            pass

    def stopped(self): # need a self.stop() for anonThread

        choice = QMessageBox.question(self,'Stop', "Are you sure you want to stop? You will not be able to pick up exactly where you left off.",
                              QMessageBox.Yes | QMessageBox.No)
        if choice == QMessageBox.Yes:
            self.stop_flag = 1
            #self.stopbtn.blockSignals(True)
            #self.paused.blockSignals(True)
        else:
            pass


    def open_dir(self):
        self.dir_name = str(QFileDialog.getExistingDirectory(self, "Select Directory"))
        if len(self.dir_name) is not 0:
            self.anon_flag = 0




def main():
    app = QApplication(sys.argv)
    main_window = MainWindow()
    main_window.show()
    sys.exit(app.exec_())

if __name__ == "__main__":
    main()

Upvotes: 0

Views: 1124

Answers (2)

eyllanesc
eyllanesc

Reputation: 244291

It is advisable not to access the flags directly, it is better to do it through the functions to make use of it transparently, for this the same class should verify the tasks.

Also it is good to give a small delay so that the application can deal with the graphic part, another possible improvement is to avoid usat sys.exit, you could call the close method that closes the window.

In the following code I have implemented the stop and pause methods.

class anonymizeThread(QThread):
    def __init__(self):
        QThread.__init__(self)
        self.onRunning = True
        self.onStop = False


    def __del__(self):
        self.wait()

    def stop(self):
        self.onStop = True

    def pause(self):
        if self.isRunning():
            self.onRunning = not self.onRunning


    def run(self):
            i = 0
            #flag = self.stop_flag
            while i < 10000000:
                if self.onRunning: # and self.stop_flag is not 1:
                    print(i)
                    i+=1

                if self.onStop:
                    break
                QThread.msleep(10)
            print('i didnt enter the loop')


class MainWindow(QMainWindow, Ui_MainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.setupUi(self)


        # connect the buttons
        self.worker = anonymizeThread()
        self.anonbtn.clicked.connect(self.anonymize)
        self.pause.clicked.connect(self.paused)
        self.stopbtn.clicked.connect(self.stopped)

        # block button signals to start
        self.pause.blockSignals(True)
        self.stopbtn.blockSignals(True)

    def close_application(self):
        choice = QMessageBox.question(self, 'Just had to check...', "Are you sure you want to exit?", QMessageBox.Yes | QMessageBox.No)
        if choice == QMessageBox.Yes:
            self.close()

    def anonymize(self):
        self.pause.blockSignals(False)
        self.stopbtn.blockSignals(False)
        self.worker.start()


    def paused(self):
        self.worker.pause()

    def stopped(self): # need a self.stop() for anonThread

        choice = QMessageBox.question(self,'Stop', "Are you sure you want to stop? You will not be able to pick up exactly where you left off.",
                              QMessageBox.Yes | QMessageBox.No)
        if choice == QMessageBox.Yes:
            self.worker.stop()

Upvotes: 1

JoshG
JoshG

Reputation: 13

Thanks to @eyllansec and @ekhumoro..

In the above code, all instances of self.stop_flag = ... should have been self.worker.stop_flag = ... as it is changing the variable that is to be used in the worker class/thread. My mistake was assuming both classes inherited the same "self".

If there are other errors and or better explanations of what I did incorrectly, please do post an answer and I'll accept it!

Upvotes: 0

Related Questions