Stopping a running QProcess slows down GUI (Windows)
-
Hello.
I have a GUI application that launches a qprocess. This qprocess calls up a long-running console program in the background which spits out a bunch of stdout. When there is something new from that qprocess on stdout, it will emit a signal with that stdout line and is slotted by the GUI and displayed in a Text Browser widget. It works great if I just let it run and exit on its own. But if I try to interrupt the qprocess with a stop button, which sends a .kill() command to the qprocess, it will severely slow down my GUI. The GUI still works, its just really slow. Text fields are slow to respond. Even dragging the GUI window around is choppy and skips around. If I do too many things in the GUI in this condition, it can crash the GUI; like if I mouse over the menu bar items and quickly move the mouse to drag the MainWindow and then go and click some text field, it will freeze and grey out the whole GUI like it is trying to buffer all my functions, and it may follow through with the last action or it may just crash and close.I narrowed it down to the qprocess being killed.
I wrote a side program to monitor PIDs and confirm that when I press the stop button, the process indeed is killed and the GUI is the only pid running. So somehow a killed off qprocess causes the GUI to jam up. Not sure how to get over this. Thoughts?
-
Hello.
I have a GUI application that launches a qprocess. This qprocess calls up a long-running console program in the background which spits out a bunch of stdout. When there is something new from that qprocess on stdout, it will emit a signal with that stdout line and is slotted by the GUI and displayed in a Text Browser widget. It works great if I just let it run and exit on its own. But if I try to interrupt the qprocess with a stop button, which sends a .kill() command to the qprocess, it will severely slow down my GUI. The GUI still works, its just really slow. Text fields are slow to respond. Even dragging the GUI window around is choppy and skips around. If I do too many things in the GUI in this condition, it can crash the GUI; like if I mouse over the menu bar items and quickly move the mouse to drag the MainWindow and then go and click some text field, it will freeze and grey out the whole GUI like it is trying to buffer all my functions, and it may follow through with the last action or it may just crash and close.I narrowed it down to the qprocess being killed.
I wrote a side program to monitor PIDs and confirm that when I press the stop button, the process indeed is killed and the GUI is the only pid running. So somehow a killed off qprocess causes the GUI to jam up. Not sure how to get over this. Thoughts?
-
@PyMan Did you debug your app to see what happens?
How exactly do you kill the process? You should show relevant code.@jsulm yea. Here is what I could strip down but basically represents the areas that represent generating the qprocess and stopping the qprocess.
Here is how the qprocess is generated:
def launch_cmd(self): '''Launches the qprocess and assigns slots to signals.'' self.cli_process = QProcess() self.cli_process.readyReadStandardOutput.connect(self.handle_stdout) # self.cli_process.readyReadStandardError.connect(self.handle_stderr) self.cli_process.start("myprog.exe", ["run", "all", "--debug"])Here is the stop slot function, which is actually in a thread. I haven't yet tried making this as a slot inside the GUI portion of code code, its in the Thread class. cli_process was passed into the thread class for some reason.
def stop(self): '''Stops the thread and does some housekeeping''' print("Stop detected") self.abort_flag = True self.is_running = False try: self.cli_process.kill() #if self.cli_process.processId(): #os.kill(self.cli_process.processId(), signal.SIGINT) except: print(f"[{datetime.now()}]: Warning, cli process not killed...continuing headless.") else: passThanks for taking a look!
-
@jsulm yea. Here is what I could strip down but basically represents the areas that represent generating the qprocess and stopping the qprocess.
Here is how the qprocess is generated:
def launch_cmd(self): '''Launches the qprocess and assigns slots to signals.'' self.cli_process = QProcess() self.cli_process.readyReadStandardOutput.connect(self.handle_stdout) # self.cli_process.readyReadStandardError.connect(self.handle_stderr) self.cli_process.start("myprog.exe", ["run", "all", "--debug"])Here is the stop slot function, which is actually in a thread. I haven't yet tried making this as a slot inside the GUI portion of code code, its in the Thread class. cli_process was passed into the thread class for some reason.
def stop(self): '''Stops the thread and does some housekeeping''' print("Stop detected") self.abort_flag = True self.is_running = False try: self.cli_process.kill() #if self.cli_process.processId(): #os.kill(self.cli_process.processId(), signal.SIGINT) except: print(f"[{datetime.now()}]: Warning, cli process not killed...continuing headless.") else: passThanks for taking a look!
@PyMan said in Stopping a running QProcess slows down GUI (Windows):
which is actually in a thread
Why a thread?
If the app you're starting as another process is your application you could let it read from stdin and then write to it via QProcess to tell it to terminate.
Or simply use https://doc.qt.io/qt-6/qprocess.html#terminateIt is also still not clear what happens after calling kill (why your app is becoming slow).
-
Is there a way to use QProcess.write() to send a Ctrl+C and allow the QProcess to exit on its own? That would solve the problem. Literally I have narrowed it down to the .kill() command, if I can avoid that, I'll be golden.
@PyMan said in Stopping a running QProcess slows down GUI (Windows):
Is there a way to use QProcess.write() to send a Ctrl+C and allow the QProcess to exit on its own?
I don't think that would work. I think it requires a Ctrl+C on the attached console, not character 3 on its standard input.
We don't know what your
myprog.exe run all --debugdoes or howmyprog.exebehaves. I would begin by trying with some other command (not yours) and see how it behaves.One point: your
try ... exceptshould be a waste of time, Qt code (e.g.QProcess.kill()) does not raise exceptions. -
@PyMan said in Stopping a running QProcess slows down GUI (Windows):
which is actually in a thread
Why a thread?
If the app you're starting as another process is your application you could let it read from stdin and then write to it via QProcess to tell it to terminate.
Or simply use https://doc.qt.io/qt-6/qprocess.html#terminateIt is also still not clear what happens after calling kill (why your app is becoming slow).
@jsulm I have multiple threads running. Each thread has an associated call to the external myprog.exe and needs to read the STDOUT of it in order to make calculations.
the myprog.exe is not my code, it is a client's program. That program connects to a com port. There are 6 total com ports that the myprog.exe works with asynchronously. This is why the QProcess termination is controlled by a thread. It is controlled by a thread because the user needs to be able stop that individual myprog.exe called that is connected to that com port. I do not have control of what goes between the comport and myprog.exe. Like I said, when it runs to completion and exits gracefully, it doesn't affect the GUI. I will try to do a minimal viable mockup to post here. But I dont want to overcomplicate.
That said, I tried the QProcess.terminate() and it does nothing. Reading the doc, I see that:
Console applications on Windows that do not run an event loop, or whose event loop does not handle the WM_CLOSE message, can only be terminated by calling kill().
So my guess is myprog.exe does not handle WM_CLOSE. I could ask the client to put that feature in I guess, but they may look at me sideways. Ctrl+C is how we kill it normally. TKinter does not have this issue. I can send a Ctrl+C to it via subprocess and it has no affect on the TKInter GUI.
I don't know why the QProcess.kill() affects the GUI. I have taken the kill command out of the thread's stop() method and put it directly in the main application code where the stop button slot is, and it does the same exact thing. I can see it does successfully kill the QProcess.
-
@jsulm I have multiple threads running. Each thread has an associated call to the external myprog.exe and needs to read the STDOUT of it in order to make calculations.
the myprog.exe is not my code, it is a client's program. That program connects to a com port. There are 6 total com ports that the myprog.exe works with asynchronously. This is why the QProcess termination is controlled by a thread. It is controlled by a thread because the user needs to be able stop that individual myprog.exe called that is connected to that com port. I do not have control of what goes between the comport and myprog.exe. Like I said, when it runs to completion and exits gracefully, it doesn't affect the GUI. I will try to do a minimal viable mockup to post here. But I dont want to overcomplicate.
That said, I tried the QProcess.terminate() and it does nothing. Reading the doc, I see that:
Console applications on Windows that do not run an event loop, or whose event loop does not handle the WM_CLOSE message, can only be terminated by calling kill().
So my guess is myprog.exe does not handle WM_CLOSE. I could ask the client to put that feature in I guess, but they may look at me sideways. Ctrl+C is how we kill it normally. TKinter does not have this issue. I can send a Ctrl+C to it via subprocess and it has no affect on the TKInter GUI.
I don't know why the QProcess.kill() affects the GUI. I have taken the kill command out of the thread's stop() method and put it directly in the main application code where the stop button slot is, and it does the same exact thing. I can see it does successfully kill the QProcess.
-
@PyMan hi,
Out of curiosity, since you are mentioning serial ports, why not use QSerialPort and handle that part directly in your application ?
@SGaist said in Stopping a running QProcess slows down GUI (Windows):
part directly in your application
Yes I would normally do that, but myprog.exe is doing that and I have no control over myprog.exe. There are things under the hood that I'm not consigned to do. This is strictly a GUI project and launches and controls an approved application, several instances of it actually.
-
@JonB said in Stopping a running QProcess slows down GUI (Windows):
k. I think it requires a Ctrl+C on the attached console, not character 3 on its standard inp
I confirmed this, you are exactly correct. Since QProcess.kill() can't send anything, I tried os.kill() because I can send signals. I experimented with
os.kill(QProcess.processID, signal.CTRL_C_EVENT)and that sends the ctrl+c to the upper python console running the whole show and force quits the entire GUI. :(
I consider this a bit buggy, I mean QProcess.kill() is working, the only issue with it is what's happening to the GUI after the fact. The GUI still works to some extent, but definitely a slowdown is happening and doesn't like me to click things too fast. I'll try to get a demo together. Something about QProcess is intimately tied to the GUI, maybe its because the ungraceful end of the QProcess affected how the PyQT GUI is dealing with a hanging signal out there that just abruptly ended? How does that signal / slot mechanism work under the hood? Maybe the PyQT GUI is trying to poll a process that no longer exists? I think this is bugreport-worthy.