I'm trying to create a thread, and end it when the frame of a wxPython app is closed. Here's my code:
#! /usr/bin/env python
import time, wx
from threading import Thread
class UpdateThread(Thread):
def __init__(self):
self.stopped = False
Thread.__init__(self)
def run(self):
while not self.stopped:
self.updateExchange()
time.sleep(1)
def updateExchange(self):
print("Updated...")
class tradeWindow(wx.Frame):
def __init__(self, parent, id):
wx.Frame.__init__(self, parent, id, "Exchange", size = (500, 190))
panel = wx.Panel(self)
def OnExit(self):
tickThread.stopped # I've also tried: tickThread.stopped = True
tickThread = UpdateThread()
tickThread.start()
if __name__ == "__main__":
app = wx.PySimpleApp()
frame = tradeWindow(parent = None, id = -1)
frame.Show()
app.MainLoop()
But when I close the frame, it keeps printing.
There is no magic Frame.OnExit method. You're mixing up frames and apps. Frames, like other windows, close. Apps exit.
So, you could put your code in your app class's OnExit method. But that's not what you want here.
Look at this simple tutorial for the OnExit method. Again, it's not what you want here, but you should know how it works (and which object it gets called on).
You can always bind EVT_CLOSE to call anything you want in your window. But you have to do this explicitly.
Normally, you'd call the method OnClose or OnCloseWindow. Calling it OnExit is just going to lead to major confusion (as it has).
The event handler method that you bind to has to actually be an event handler, meaning it takes an event parameter (as well as the self).
Next, if you add an EVT_CLOSE handler, you're overriding the default one, meaning that Destroy never gets called unless you do it yourself.
Here's a tutorial on binding EVT_CLOSE, which shows all of the above steps.
Finally, as DavidRobinson explained, just doing tickThread.stopped won't do anything; you have to set it to True.
Putting it all together:
class tradeWindow(wx.Frame):
def __init__(self, parent, id):
wx.Frame.__init__(self, parent, id, "Exchange", size = (500, 190))
panel = wx.Panel(self)
self.Bind(wx.EVT_CLOSE, self.OnClose)
def OnClose(self, event):
tickThread.stopped = True
self.Destroy()
One more note:
In any serious threaded program, if you want to share a value between threads, you generally need to synchronize it with some kind of sync object. If you're waiting for a signal from another thread, the typical way to handle that is with a Condition. If, on the other hand, you just want to share the value, you can use a Lock.
If you really know what you're doing, you can often get away with letting the Global Interpreter Lock handle synchronization for you. But in general, this is a bad idea. For example, your main thread could be running on core 0, and your background thread on core 1, and there is nothing in the Python language definition that guarantees that the computer will ever copy the updated value from core 0's cache to core 1's. So, your background thread could be spinning forever, watching the old value and never getting the new value to look at. As it turns out, with CPython 2.0-3.3 on an x86, this can't possibly happen with your code—but on less you can prove that (or at least identify the cases that are safe), don't count on it.
Finally, you asked whether a daemon thread is an appropriate solution. From the docs:
The significance of this flag is that the entire Python program exits when only daemon threads are left.
In other words, your program can exit without having to stop your daemon threads. But…
Note Daemon threads are abruptly stopped at shutdown. Their resources (such as open files, database transactions, etc.) may not be released properly. If you want your threads to stop gracefully, make them non-daemonic and use a suitable signalling mechanism such as an Event.
Try setting tickThread.setDaemon(True) before tickThread.start() - Daemon threads should exit with their parent threads.
Related
I have noticed that there are a lot of users, myself included, who don't quite grasp the concept of signals and slots in Qt. I was hoping to get some clarification on the following:
#I have a function that runs as soon as the GUI is built, this takes the information from
#a list and puts it into a string which is then uploaded to a texbox. At the bottom of this
#loop, I want it to call a function in the parent thread via signals and slots, as
#recommended by other users.
class MainWindow(QtGui.QMainWindow):
#all the code needed to build the GUI
thread_mythread = threading.Thread(target = self.updateText, args = ())
thread_mythread.start()
def clearText(self):
self.TextEdit.clear()
def updateText(self):
self.trigger.connect(self.clearText)
while True:
self.trigger.emit()
NewString = list.pop(0)
#I think I may have to use append, as setText() is not safe outside of the parent thread
self.TextEdit.append(NewString)
Although probably terribly incorrect, I attempt to use signals. Is this the proper way to do it? I also get an error that says that the Main Window object has no attribute "trigger",why is this?
thank you.
The reason you get that error is exactly the reason described by the error message - the signal trigger has not been defined anywhere in your class. You need to define it before you can emit it.
Signals and slots are used to communicate between different objects. In your example you are trying to do everything from within your MainWindow class and there is no interaction with other objects. You also only need to make the call to connect() once. You would typically call this either in the class constructor or from your main function after instantiating the objects you want to connect together.
Take a look at http://pyqt.sourceforge.net/Docs/PyQt4/new_style_signals_slots.html for some examples of how to use signals and slots properly in PyQt.
For threading, use QThread rather than threading.Thread as it is better integrated with the Qt framework. This post shows some simple examples of how to use QThread in PyQt. The second method (using moveToThread()) is considered to be the most correct way to create new threads.
The basic idea for your kind of problem is:
handle GUI operations from the main thread
handle blocking operations (in your case the while loop) in a separate thread
emit signals from the worker thread to call functions (slots) in the main thread and vice versa
Also note that:
You cannot call any methods of QWidget its descendents from a secondary thread
Signals can also send data if you need to pass it between threads
To add to #user3419537 good answer. A very quick threading example:
from PyQt4.QtCore import QObject, pyqtSlot, pyqtSignal, QThread, \
Q_ARG, Qt, QMetaObject
class MyWorker(QObject):
# define signal
clear = pyqtSignal()
update_text_signal = pyqtSignal(str) # passes a string back
finished = pyqtSignal()
def __init__(self, parent=None):
super(MyWorker, self).__init__(parent)
# Add functions etc.
#pyqtSlot(list)
def update_text(self, string_list):
#Intensive operation
self.clear.emit() # moved outside of while
while(True):
#This is infinite loop so thread runs forever
new_string = self.string_list.pop(0)
self.update_text_signal.emit(new_string) # Fixed this line
#Finished
self.finished.emit()
Then in your MainWindow class
self.my_thread = QThread()
self.handler = MyWorker()
self.handler.moveToThread(self.my_thread)
self.handler.clear.connect(self.clearText)
self.handler.update_text_signal.connect(self.update_line_edit)
self.handler.finished.connect(self.my_thread.quit)
# Start Thread
self.my_thread.start()
#pyqtSlot(str)
def update_line_edit(self, text):
self.TextEdit.append(text)
QMetaObject.invokeMethod(self.handler, 'update_text',
Qt.QueuedConnection,
Q_ARG(list, string_list))
You will need to call self.my_thread.quit() before your application closes to stop the thread and avoid the error: QThread: Destroyed while thread is still running
Please read docs for QMetaObject.invokeMethod.
Here's an example:
class DemoFrame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent)
self.panel = wx.Panel(self, -1)
...
initialize other elements
...
self.DoStuff()
def DoStuff(self):
self.panel.SetBackGroundColour(wx.Colour(240, 240, 240))
...
do something
...
Now as you know this is definitely not a good example of initializing your GUI since do something would most probably freeze the GUI while it's running, so I tweaked it to this:
import threading
class DemoFrame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent)
self.panel = wx.Panel(self, -1)
...
initialize other elements
...
DoStuffThead = threading.Thread(target = self.DoStuff, ())
DoStuffThead.start()
def DoStuff(self):
wx.CallAfter(self.ChangeBG, )
...
do something
...
def ChangeBG(self):
self.panel.SetBackGroundColour(wx.Colour(240, 240, 240))
Above code should work exactly the same as the first one does when do something is blank, but to my surprise I noticed there's little background drawing glitches when running the latter codes.
What part went wrong? Isn't this the right way to update GUI in threads?
It's a bad approach to update GUI from worker thread, not event saying it's not thread safe.
You have to communicate with main thread to update GUI.
The best way to achieve desired result is to user wx.PostEvent method. You can create custom events for your needs, inheriting from wx.PyEvent, and you better inherit threading.Thread to keep window you want to communicate within that thread class as an instance variable.
The best illustration of how to update GUI having long-running task can be found in wxPython wiki (first example).
After searching and playing with wxpython for a while, I finally found a solution for this, and it's actually quite simple, just refresh the panel and everything will be all right(add this line into ChangeBG method): self.panel.refresh(). I've no idea why the glitch exists though.
As to Rostyslav's answer, thanks a lot mate!
"It's a bad approach to update GUI from worker thread:", I think you mean it's rude to directly insert GUI codes into worker thread(which is exactly what I did in the first example) in terms of thread-safety concern, basically those GUI codes should be wrapped into thread-safe method(which is exactly what I was trying to do in my second example) and then queued into GUI main thread.
I found that there are basically three thread-safe methods as to GUI updating in worker thread: wx.PostEvent, wx.CallAfter and wx.CallLater, but I never liked wx.PostEvent, it's kind of cumbersome and you have to come up with your own event too, that's why wx.CallAfter is a better choice for me, it's more pythonic and easy to use, and actually wx.CallAfter is like a high level wrapper for wx.PostEvent if you check out the source code in _core.py:
def CallAfter(callable, *args, **kw):
"""
Call the specified function after the current and pending event
handlers have been completed. This is also good for making GUI
method calls from non-GUI threads. Any extra positional or
keyword args are passed on to the callable when it is called.
:see: `wx.CallLater`
"""
app = wx.GetApp()
assert app is not None, 'No wx.App created yet'
if not hasattr(app, "_CallAfterId"):
app._CallAfterId = wx.NewEventType()
app.Connect(-1, -1, app._CallAfterId,
lambda event: event.callable(*event.args, **event.kw) )
evt = wx.PyEvent()
evt.SetEventType(app._CallAfterId)
evt.callable = callable
evt.args = args
evt.kw = kw
wx.PostEvent(app, evt)
Well, I never tried wx.PostEvent implementation in my app, but I'm sure it would work as well.
Oh also I found this article very helpful: wxPython and Threads
I'm trying to learn how to use the thread module. I followed along with the instructions here: http://effbot.org/zone/tkinter-threads.htm
My hope is the test script will:
Print out the "count" every two seconds
Show a pop-up dialog window (also every 2 seconds)
The pop-ups should be allowed to accumulate (if I don't click "OK" for a while, there should be
multiple pop-ups)
However, when I run this script it will freeze the main window and after a while crash. I think I'm not implementing the thread module correctly.
Could someone please have a look and point out what I'm doing wrong?
Here is what I've tried so far:
from Tkinter import *
import thread
import Queue
import time
class TestApp:
def __init__(self, parent):
self.super_Parent = parent
self.main_container = Frame(parent)
self.main_container.pack()
self.top_frame = Frame(self.main_container)
self.top_frame.pack(side=TOP)
self.bottom_frame = Frame(self.main_container)
self.bottom_frame.pack(side=TOP)
self.text_box = Text(self.top_frame)
self.text_box.config(height=20, width=20)
self.text_box.pack()
self.queue = Queue.Queue()
self.update_me()
def show_popup(self):
self.my_popup = Toplevel(self.main_container)
self.my_popup.geometry('100x100')
self.popup_label = Label(self.my_popup, text="Hello!")
self.popup_label.pack(side=TOP)
self.pop_button = Button(self.my_popup, text="OK", command=self.my_popup.destroy)
self.pop_button.pack(side=TOP)
def write(self, line):
self.queue.put(line)
def update_me(self):
try:
while 1:
line = self.queue.get_nowait()
if line is None:
self.text_box.delete(1.0, END)
else:
self.text_box.insert(END, str(line))
self.text_box.see(END)
self.text_box.update_idletasks()
except Queue.Empty:
pass
self.text_box.after(100, self.update_me)
def pipeToWidget(input, widget):
widget.write(input)
def start_thread():
thread.start_new(start_test, (widget,))
def start_test(widget):
count = 0
while True:
pipeToWidget(str(count) + "\n", widget)
count += 1
time.sleep(2)
widget.show_popup()
root = Tk()
widget = TestApp(root)
start_button = Button(widget.bottom_frame, command=start_thread)
start_button.configure(text="Start Test")
start_button.pack(side=LEFT)
root.title("Testing Thread Module")
root.mainloop()
I can't reproduce your problem, but I can see why it would happen.
You're using the queue to pass messages from the background thread to the main thread for updating text_box, which is correct. But you're also calling widget.show_popup() from the background thread, which means it creates and displays a new Toplevel in the background thread. That's not correct.
All UI code must run in the same thread—not all UI code for each top-level window, all UI code period. On some platforms, you may get away with running each window in its own thread (or even free-threading everything), but that isn't supposed to work, and definitely will crash or do improper things on some platforms. (Also, that single UI thread has to be the initial thread on some platforms, but that isn't relevant here.)
So, to fix this, you need to do the same dance for creating the popups that you do for updating the textbox.
The obvious way to do that is to move the widget.show_popup() to the loop in update_me(). If you want it to happen 2 seconds after the textbox updates, just add self.top_frame.after(2000, self.show_popup) to the method.
But I'm guessing you're trying to teach yourself how to have multiple independent updating mechanisms, so telling you "just use a single update queue for everything" may not be a good answer. In that case, just create two queues, and a separate update method servicing each queue. Then, do your pipeToWidget, sleep 2 seconds, then pipeToPopup.
Another way around this is to use mtTkinter. It basically does exactly what you're doing, but makes it automatic, pushing each actual Tk GUI call onto a queue to be run later by the main loop. Of course your objects themselves have to be thread-safe, and this also means that you have to deal with the GUI calls from one thread getting interleaved with calls from another thread. But as long as neither of those is a problem (and they don't seem to be in your case), it's like magic.
If you want to know why this is freezing and/or crashing for you on Win7 and not for me on OS X 10.8… well, you really need to look into a mess of Tcl, C, and Python code, and also at how each thing is built. And, unless it's something simple (like your Tk build isn't free-threaded), it wouldn't tell you much anyway. The code isn't supposed to work, and if it seems to work for me… that probably just means it would work every time until the most important demo of my career, at which point it would fail.
I have a relatively large application written in Python and using PyQT as a GUI frontend. The entire application is in one class, in one file.
Here's an example code:
class Application(QMainWindow):
def __init__(self):
super(etc...)
self.connect(self.mainBtn, SIGNAL("clicked()"), self.do_stuff)
def do_stuff(self):
<checking some parameters>
else:
do_some_other_long_stuff()
def do_some_other_long_stuff(self):
500 lines of code of stuff doing
However, this is the problem: when I click the mainBtn, everything goes fine, except the GUI kind of freezes - I can't do anything else until the function is performed (and it's a web scraper so it takes quite a bit of time). When the function do_some_other_long_stuff ends, everything goes back to normal. This is really irritating.
Is there a way to somehow "background" the do_some_other_stuff process? I looked into QThreads and it seems it does just that, however that would require me to rewrite basically all of code, put half of my program in a different class, and therefore have to change all the variable names (when getting a variable from GUI class and putting it in working class)
Duplicate of Handling gui with different threads,
How to keep track of thread progress in Python without freezing the PyQt GUI?, etc.
Your do_stuff() function needs to start up the computing thread and then return. Multi-threading is the name given to running multiple activities in a single process - by definition if something is going on "in the background", it's running on a separate thread. But you don't need to split functions into a different classes to use threads, just be sure that the computing functions don't do anything with the GUI and the main thread doesn't call any of the functions used by the computing thread.
EDIT 10/23: Here's a silly example of running threads in a single class - nothing in the language or the threading library requires a different class for each thread. The examples probably use a separate class for processing to illustrate good modular programming.
from tkinter import *
import threading
class MyApp:
def __init__(self, root):
self.root = root
self.timer_evt = threading.Event()
cf = Frame(root, borderwidth=1, relief="raised")
cf.pack()
Button(cf, text="Run", command=self.Run).pack(fill=X)
Button(cf, text="Pause", command=self.Pause).pack(fill=X)
Button(cf, text="Kill", command=self.Kill).pack(fill=X)
def process_stuff(self): # processing threads
while self.go:
print("Spam... ")
self.timer_evt.wait()
self.timer_evt.clear()
def Run(self): # start another thread
self.go = 1
threading.Thread(target=self.process_stuff, name="_proc").start()
self.root.after(0, self.tick)
def Pause(self):
self.go = 0
def Kill(self): # wake threads up so they can die
self.go = 0
self.timer_evt.set()
def tick(self):
if self.go:
self.timer_evt.set() # unblock processing threads
self.root.after(1000, self.tick)
def main():
root = Tk()
root.title("ProcessingThread")
app = MyApp(root)
root.mainloop()
main()
I'm in a bind, since this is being written on a classified machine I am unable to copy+paste here. Being somewhat a novice, my approach is probably unorthodox.
I have a GUI written in Tkinter with several buttons. Each button is linked to a class that, in effect, runs a short script. When the button is clicked, I inititalize a class log_window which is simply a Tkinter text widget. I then create a global variable linking log to the log_window I just created, and as the script runs I pipe sys.stdout/stderr to log (I created a write method specifically for this). Everything is kosher, except that the log_window text widget doesn't update with my piped stdout until after the class calling it is finished. However, if I simply print within the class, it will print in the order it is called.
Example
import Tkinter
from Tkinter import *
import time
class log_window:
def __init__(self,master):
self.textframe = Tkinter.Frame(master)
self.text = Text(self.textframe)
self.text.pack()
self.textframe.pack()
def write(self,text):
self.text.insert(END,text)
class some_func1: # This effectively waits 5 seconds then prints both lines at once
def __init__(self,master):
log.write("some text")
time.sleep(5)
log.write("some text")
class some_func2: # This prints the first object, waits 5 seconds, then prints the second
def __init__(self,master):
print "some text"
time.sleep(5)
print "some text"
if __name__ == '__main__':
global log
root = Tk()
log = log_window(root)
root.after(100,some_func1, root)
root.after(100,some_func2, root)
root.mainloop()
Sorry if my example is a little bit muffed, but I think it makes the point. The piping I do is through Popen and some system calls, but they aren't part of the issue, so I only highlighted what, I presume, is the LCD of the issue.
I don't know the details of Tkinter's concurrency, but fiddling around reveals that if you put
master.update_idletasks()
after each call to log.write, it updates on cue. You could give log a .flush() method to do that (like file handles have), or you could just make log.write call it after writing.
When you call sleep it causes your whole GUI to freeze. You must remember that your GUI runs an event loop, which is an infinite loop that wraps all your code. The event loop is responsible for causing widgets to redraw when they are changed. When a binding is fired it calls your code from within that loop, so as long as your code is running, the event loop can't loop.
You have a couple of choices. One is to call update_idletasks after adding text to the widget. This lets the event loop service "on idle" events -- things that are schedule to run when the program isn't doing anything else. Redrawing the screen is one such event, and there are others as well.
The other option is to run your functions in a thread or separate process. Because Tkinter isn't thread safe, these other threads or processes can't directly communicate with the GUI. What they must do is push a message onto a queue, and then your main (GUI) thread must poll the queue and pull messages off. It would be easy to build this code into your log class, and polling the queue can be done using the event loop -- just write a method that pulls messages off the queue and inserts them into the widget, the calls itself using after a few hundred milliseconds later.
You have to update your widget content by adding self.text.update() after self.text.insert(END,text)