I'm writing a simple GUI for the Lights Out Board (Wiki) using PyQt4. The GUI has a 'Solve' Button which uses Iterative Deepening Depth First Search. This being time consuming, I spawn a new QThread to solve for the puzzle and update the board when it finishes while the GUI remains responsive. This I'm able to do.
But, I also have a 'Stop' Button which should stop the Search thread if it is currently running and I'm unable to stop the QThread using the exit(). Here's the code for the three functions.
class LightsOut(QWidget):
def __init__(self, parent=None):
# Whole other initialization stuff
self.pbStart.clicked.connect(self.puzzleSolver) # The 'Start' Button
self.pbStop.clicked.connect(self.searchStop) # The 'Stop' Button
def searchStop(self):
if self.searchThread.isRunning():
self.searchThread.exit() # This isn't working
self.tbLogWindow.append('Stopped the search !') # This is being printed
else:
self.tbLogWindow.append('Search is not running')
def searchFinish(self):
self.loBoard.setBoard() # Redraw the lights out board with solution
def puzzleSolver(self):
maxDepth = self.sbMaxDepth.value() # Get the depth from Spin Box
self.searchThread = SearchThread(self.loBoard, self.tbLogWindow, maxDepth)
self.searchThread.finished.connect(self.searchFinish)
self.tbLogWindow.append('Search started')
self.searchThread.start()
When I click the 'Stop' Button, in the Log Window (QTextBrowser), I can see 'Stopped the search' message, but my CPU is still running at 100% and when the search finishes, the solution is being displayed (searchFinish is being called). Obviously, I'm missing something very simple, and I haven't used terminate() as it is frowned upon in the documentation.
Use terminate() instead of quit and call wait(). wait() will block until the QThread has finished.
Another thing you could do is to set a quit condition outside the thread which you will check for inside the thread (Probably the best solution). Additionally you can connect a slot to the finished signal.
Related
I have a GUI application which has a start button that slots to a function which will eventually launch a qprocess. This qprocess calls up a long-running console program in the background which spits out a bunch of stdout. The stdout is captured with signals and slots. 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 the detached qprocess run and exit on its own. The GUI remains responsive and everything is great. There is a stop button on the GUI to stop it all too, its important to be able to stop it if needed. I built a slot for the stop button which kills the Qprocess and resets everything to square one. If I press the stop button before the qprocess gets invoked, everything works fine and GUI remains responsive.
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 if not crash it if I try click too many things.
Here is how the qprocess is generated:
def launch_cmd(self):
'''Look for the COM port number the uut/DUT is registered as.'''
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 .kill() inside the mainWindow 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: pass
The GUI still works when the stop button is pressed after qprocess is killed, 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?
I have a thread that is running and a 'stop thread' button which when clicked calls a method that calls the terminate() of the thread. I have a QMessage Box that confirms the stop button before it terminates. The functionality is as expected but if I then start the thread again and click the close button then I get the confirmation MessageBox, I then select confirm and the thread stops but another confirmation box appears. It continues in this manner - if I run it 3 times I have to confirm three boxes, 4 I get 4 etc. The first box will terminate the thread in all cases. It seems like the MessageBox is not being destroyed properly each time it appears so they just build up? Not sure what's going on.
See below code. I have a thread 'my_thread' and a button connected to the function 'stop_thread' outlined below.
def stop_thread(self):
prompt=QWidgets.QMessageBox.question(self, 'Stop!', 'Are you sure you want to stop thread?', QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No)
if prompt == QtWidgets.QMessageBox.Yes:
self.my_thread.terminate()
else:
pass
I would expect each time I start and then stop the thread using the button I get one confirmation box appearing and upon selecting 'Yes' the thread will terminate. In reality the thread terminates but I get multiple MessageBoxes appearing one after the other - the number of them corresponds to how many times I have started the thread.
Solved it - It was because I was connecting the stop_button to the function outlined above in the function that I used to start the thread. So every time I started a thread I connected it again and called the function an extra time. What I didn't know was you could connect buttons to a function more than once (or multiple functions) but you learn something new everyday.
To fix I moved the connection of the stop button to the stop_thread function to the main class and now works great.
I'm new to event-driven programming and I would really like to understand better what happens under the hood, when the CPython interpreter goes through the code line by line. So far I have only programmed sequentially, and I have a fairly good idea in mind how the interpreter converts my code into bytecode and then goes from one statement to the next and executes the commands.
But for event-driven programming, I'm totally confused how the interpreter works.
In particular, I'm confused
how the interpreter knows where to jump next in the source code
also how function handlers are called when certain events happen
how the refresh rate of the event loop is handled: Is actually all the code of the function handles run thousand of times per second, but not executed because some kind of "event-has-not-happened" flag say "don't execute this function now"?
To make this discussion more concrete, could you illustrate these points on the following example, taken from this site:
from Tkinter import *
ROOT = Tk()
def ask_for_userinput():
user_input = raw_input("Give me your command! Just type \"exit\" to close: ")
if user_input == "exit":
ROOT.quit()
else:
label = Label(ROOT, text=user_input)
label.pack()
ROOT.after(0, ask_for_userinput)
LABEL = Label(ROOT, text="Hello, world!")
LABEL.pack()
ROOT.after(0, ask_for_userinput)
ROOT.mainloop()
Ideally I'd like an explanation in a similar spirit to this article, where it is brilliantly explained from the point of view of how the CPython interpreter works, why some statements are thread-safe and some not, and how thread-safety is achieved.
All that an event loop does is call other functions when an event takes place. The graphical subsystem helps out here, by signalling to the event loop that events are waiting to be processed.
Events like keyboard input and mouse interactions (moving the pointer, clicking) are all handled by the graphical subsystem (GUI) and the operating system (OS). Keyboards and mice are hardware devices, and computers use interrupts to record their state for the GUI to pick up.
If you don't touch your keyboard or mouse, an event loop can just do nothing; the loop blocks, and the OS will execute other processes as the loop has signalled it is waiting for something to happen. The OS is in control at this point, the process is not given any CPU time and other processes run instead. Once something happens, then there are events in the queue and the OS can resume the process. Imagine a function call in the event loop that asks if there are more events, and that call won't return until there are.
Once the loop resumes, there are events in the queue to process ('mouse position is now x, y', 'the keyboard input queue contains the characters F, O, and O'). Each event can trigger code you wrote, and registered to be run on that event. For example, you can register a handler to be run when a button is clicked; the event framework has a registry that if the conditions are right ('mouse button click' event happened, cursor is at the right location on the screen, button is active and visible) so knows to call your custom event handler.
Such an event handler is entirely synchronous, if the handler takes a long time to complete you'll notice that your GUI 'freezes', does nothing else, as Python is too busy running that one handler. The usual work-around is to use threads in that case; your event handler quickly starts a separate thread to do the real work, and returns. That way the main thread (with the event loop) can handle the next event, while the OS switches between the work in the extra thread and the main thread.
As to the specific piece of code you posted, that's actually not a very good example to use. It actively ignores GUI input, using the raw_input() function to capture keyboard input from the console instead. The GUI is entirely blocked every time the function runs!
The ask_for_userinput() function is an event handler, it is registered as one with the after() method. after() uses a timer interrupt, (usually implemented with a SIGALRM interrupt) to be called after at least 0 seconds have passed (so as soon as possible, really). Each time it is called it adds a new label to the GUI (just a piece of text) and re-schedules itself. It is not very interesting!
I've written a GUI program with PyQt4 that has to send a message string over a serial data link.
I have implemented a GUI interface with two button widgets. I need to send the data over the serial link continuously, once per second when the first button is clicked, and then stop when the second button is clicked.
My current program is able to send data only at the instant a button is clicked. This is the method I wrote to handle the button click signal:
def sendMessage(self):
while 1:
print "Hello........"
if checke == False:
break
Do I need to use threads to solve this problem?
It depends... if the send operation is fast, you can use the QTimer class. It integrates with the Qt event loop so you don't have to worry about threading issues. Serial communications can be slow, depending on how much data you are sending, so I can't say for sure if this is the right solution for you.
Yes. The key to GUI programming is never do any long operation on the main thread, because it'll block the whole program until that operation is complete.
If you want to continuously send data over network, do it in a background thread.
Some example code for you.
class MessageWorker(QtCore.QThread):
def __init__(self):
super(ParserWorker, self).__init__()
self.ok_to_send = False
self.terminated = True
def run(self):
while not self.terminated:
if self.ok_to_send:
self.send_message()
time.sleep(1)
def start_send():
self.ok_to_send = True
def pause_send():
self.ok_to_send = False
def terminated():
self.terminated = False
Then in the main program just call
worker = MessageWorker()
worker.start() # Start the background thread
worker.start_send() # Start sending message
worker.pause_send() # Pause sending message
worker.terminated() # Stop sending message permanently
Yes, you need to use threads. In any GUI-based program, any work that's going to take a non-trivial amount of time should always happen on a separate thread to avoid blocking the UI—whenever you see an "unresponsive" program, that's almost always due to the program failing to process window messages because its UI thread is blocked inside some long operation.
One easy way to startup a background thread is to use the threading module. Here's how you might use it to write data to the serial port once per second:
class MyClass:
# This method will run on a separate thread
def _serial_port_worker(self):
while self._run_worker:
self.send_data_to_serial_port()
time.sleep(1)
# Call this to start the worker thread
def start_worker_thread(self):
self._run_worker = True
worker_thread = threading.Thread(target=self._serial_port_worker,
args=(self,))
worker_thread.start()
# Call this to tell the worker thread to stop
def stop_worker_thread(self):
self._run_worker = False
Basically you have three options:
Use a second thread to do the serial comms. GUI toolkits aren't always thread-safe, so you should only make calls to them from the main thread. Additionally, there is a limitation to threading in Python; only one thread at a time can be executing Python bytecode.
Use the GUI toolkit's timeout function (might be called differently) to create an event every now and then. In the event handler do the serial comms. Make sure that you use non-blocking reads and writes (in pyserial, configure a timeout in the Serial object), otherwise your app might become unresponsive.
Do the serial communications from the second program using the multiprocessing module. Even if the second process blocks, it won't affect the GUI. You can use multiprocessing.Queue to communicate between the QUI and the other process.
I'm attempting to build a very simple wxPython GUI that monitors and displays external data. There is a button that turns the monitoring on/off. When monitoring is turned on, the GUI updates a couple of wx StaticLabels with real-time data. When monitoring is turned off, the GUI idles.
The way I tried to build it was with a fairly simple Python Thread layout. When the 'Start Monitoring' button is clicked, the program spawns a thread that updates the labels with real-time information. When the 'Stop Monitoring' button is clicked, thread.join() is called, and it should stop.
The start function works and the real-time data updating works great, but when I click 'Stop', the whole program freezes. I'm running this on Windows 7 64-bit, so I get the usual "This Program has Stopped Responding" Windows dialog.
Here is the relevant code:
class MonGUI(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
...
... other code for the GUI here ...
...
# Create the thread that will update the VFO information
self.monThread = Thread(None, target=self.monThreadWork)
self.monThread.daemon = True
self.runThread = False
def monThreadWork(self):
while self.runThread:
...
... Update the StaticLabels with info
... (This part working)
...
# Turn monitoring on/off when the button is pressed.
def OnClick(self, event):
if self.isMonitoring:
self.button.SetLabel("Start Monitoring")
self.isMonitoring = False
self.runThread = False
self.monThread.join()
else:
self.button.SetLabel("Stop Monitoring")
self.isMonitoring = True
# Start the monitor thread!
self.runThread = True
self.monThread.start()
I'm sure there is a better way to do this, but I'm fairly new to GUI programming and Python threads, and this was the first thing I came up with.
So, why does clicking the button to stop the thread make the whole thing freeze up?
In wxPython, GUI operations need to take place in the main thread. At places in your code you are calling the GUI from a different thread.
The easiest solution is to use wx.CallAfter(). A line of code would look like
wx.CallAfter(self.button.SetLabel, “Start Monitoring”)
which will then call self.button.SetLabel(“Start Monitoring”) from the main thread after the function completes.
There are other ways around this as well, such as using a Python threading Queue or wx.PostEvent, but start with CallAfter because it's easiest.
Other issues are also relevant, like you can't restart the same thread, but using CallAfter will stop the crashing.
It's likely hanging on join([timeout]), which blocks the calling thread until the thread whose join() method is called terminates – either normally or through an unhandled exception – or until the optional timeout occurs.
Do you have some inner loop in your thread, or a blocking call that waits for some source of data that may never come? When I wrote a basic serial program that grabbed COM port data, it would sometimes hang because a read function in my thread would block until it got something.
I would sprinkle in a few debugging print statements to see whats happening.
Edit:
I'd also use a threading.Event() instead of a Boolean flag, e.g.:
# in the init code...
self.runThread = threading.Event()
# when starting thread...
self.runThread.set()
self.monThread.start()
# in the thread...
while self.runThread.isSet():
pass # do stuff
# killing the thread...
self.runThread.clear()
self.monThread.join()
This shouldn't make it work differently, but it's a slightly safer way to do it.
tom10 has the right idea with avoiding UI updates from the monitor thread.
Also, it is probably not a good idea to have the blocking call self.monThread.join() in your UI thread. If you want the UI to give some feedback that the monitor thread has actually ended, have monThreadWorker issue a wx.CallAfter() or wx.PostEvent() just before it closes.
Avoid anything that blocks in your UI thread, and you will avoid deadlocking the UI