-2

For now everything in my application is quite simple because I'm just starting building it. In the end there will be potentially two threads which might run for a long time. Not because they've got a lot of computing to do, but because I have to coninuously listen on COM ports and record the data.

Now during normal operation this is not a problem because it does what it should. Now it can happen though that the user wants to close the application before the measurement is done and the threads are still running.

I got that working too, however I'm not sure if my approach is the right one on that.

I don't want to use terminate since I'm not sure what happends to COM ports that are not closed properly and other things.

Since it can take a while for the threads to close (even on my simple loop thread it takes a few seconds) I wanted to show some little countdown to tell the user yes the shutdown is happening, it just takes a few seconds.

I tried to show it in a QtextEdit in the main widget. But I think that does not work because I'm already in the closeEvent in my approach. I didn't try an additional QDialog popup yet, but I guess it won't work either due to the same resons.

Is there a way to make something like this work to prevent the user from thinking that the program has crashed?

So this is how far I've gotten:

My worker object:

class Thread_Simulator(QtCore.QObject):
sigFin = QtCore.Signal()
sigText = QtCore.Signal(str)

def __init__(self):
    super().__init__()
    self.quit = 0

def run(self):
    i = 0
    while i < 10:
        i += 1
        self.sigText.emit(str(i))
        if self.quit == 1:
            #self.sigText.emit(__class__+" break")
            break
        sleep(2)

    self.sigFin.emit()

def end(self):
    self.quit = 1

Where I start the thread:

    self.thread_test = QtCore.QThread()
    self.worker_test = Thread_Simulator()
    self.worker_test.moveToThread(self.thread_test)

    self.thread_test.started.connect(self.worker_test.run)
    self.worker_test.sigFin.connect(self.thread_test.quit)
    self.worker_test.sigFin.connect(self.worker_test.deleteLater)
    self.thread_test.finished.connect(self.thread_test.deleteLater)
    self.worker_test.sigText.connect(self.simulator_update)
    self.thread_test.start()

And finally my close event of the main application which happens when the user presses the x button I guess.

def closeEvent(self, event):
    self.worker_test.end()
    print("EXITING -- Waiting for Threads to shut down")
    self.thread_test.wait(10000)
    
    return super().closeEvent(event)

Another problem with this is also that if the thread is not running anymore there will be an error on close because the thread is already deleted. I might get around this by setting another variable with the finished signal of the thread and checking for that.

Maybe there is a better way to do this though. Also enabling me to show this countdown thing I thought of.


EDIT:

ok I tried things and it might be stupid but it seems to work this way. I thought that I might as well keep the thread and the worker alive until the end of the program since they don't change anyway and might get restarted. So if I keep them alive it might use up more memory but it is also quicker to restart the worker since they are still alive.

So I removed the deleteLater signal connections and call them manually in the closeEvent instead:

    self.thread_test = QtCore.QThread()
    self.worker_test = Thread_Simulator()
    self.worker_test.moveToThread(self.thread_test)

    self.thread_test.started.connect(self.worker_test.run)
    self.worker_test.sigFin.connect(self.thread_test.quit)
    self.worker_test.sigFin.connect(self.show_finished)
    #self.worker_test.sigFin.connect(self.worker_test.deleteLater)
    #self.thread_test.finished.connect(self.thread_test.deleteLater)
    self.worker_test.sigText.connect(self.simulator_update)
    self.thread_test.start()

I'm still not able to show some sort of message to inform the user about the shutdown process (the .append does not show anymore). The shutdown seems to work a little faster though and the "finised" signal gets still correctly emited by the worker.

def closeEvent(self, event): #: QtGui.QCloseEvent) -> None:
    self.log.append("EXITING -- Waiting for Threads to shut down")
    print("EXITING -- Waiting for Threads to shut down")
    self.worker_test.end()
    self.thread_test.quit()
    self.worker_test.deleteLater()
    self.thread_test.deleteLater()
    self.thread_test.wait(10000)
    
    return super().closeEvent(event)

1 Answers1

0

I still don't really know if that is the correct way to do it, but it works that way for me.

I'm initializing an additional variable (self.end = None) in the init of the main window.

My closeEvent looks like this now:

 def closeEvent(self, event):
    if self.thread_test.isRunning():
        print("Thread still running -- closing")
        self.worker_test.end()
        self.end = True
        self.show_shutdown_message()
        event.ignore()

    elif self.thread_test.isFinished():
        print("Thread finished -- ENDE")
        self.worker_test.deleteLater()
        self.thread_test.deleteLater()
        self.thread_test.wait(10000)
        event.accept()

the Slot of the sigFin signal of the worker thread checks for that and if it was set to anything else but none it will go to the closeEvent again after the worker has finished.

@QtCore.Slot()
def show_finished(self):
    print("Worker finished")
    if self.end is not None:
        self.close()

And with the call of self.show_shutdown_message() I can also show a little QDialog window which informs the user about what is happening.

If anyone knows a more "official" way to do this I'm still open for suggestions though :)