Reputation: 379
I'm using wxPython to build the GUI of my program,I am having trouble with running a function that uses a thread to get data from a file and display it in the ObjectListView, reading the data from the file runs fine but displaying the data from the thread sometimes crashes the whole program. gui.ProgressDialog is a wx.Frame that will show progress bar until the thread is finished. self.dv is an ObjectListView, an object that is derived from wx.ListCtrl, the set_data is a function I have written, that iterate over the data and adds it to the list.
def set_data(self,data):
"""set the data on the ObjectListView"""
pd = gui.ProgressDialog("Setting Data..")
def set_thread():
self.dv.set_data(data) #Takes time
pd.Close()
#opens a thread that sets the data
t = Thread(target = set_thread,name="SetDataThread")
t.start()
I have read something about wx.CallAfter
and that I should use it but I didn't understand how. I tried calling the set_data like this -
wx.CallAfter(self.dv.set_data,data)
but it didn't work either. can someone explain the functions wx.CallAfter
, wx.CallLater
, wx.PostEvent
, how can they be helpful when using threads with wxPython and which one of them should I use? or another solution for this?
Upvotes: 0
Views: 2706
Reputation: 365597
You cannot make any GUI calls from a secondary thread. This is explained in the wx.Thread
docs, but it applies to normal Python threading.Thread
threads too:
GUI calls, such as those to a wxWindow or wxBitmap are explicitly not safe at all in secondary threads and could end your application prematurely.
So, what you have to do instead is send a message to the main thread, asking it to make the GUI calls for you.
PostEvent
is the low-level solution. You create a new kind of event. You write a handler for that event type on your main thread that does some GUI work based on the information in the event. Then on your background thread, you construct one of those events with the appropriate information, and PostEvent
it, which puts it on the main thread's message queue.
The other options are basically higher-level wrappers around this. And CallAfter
is probably the one you want. The CallAfter
page on the wxPyWiki, has a great explanation and a complete sample, and there's also a nice example on the docs page itself, so I won't try to compete with them; instead, I'll try to cover your specific question.
I think what you're missing is that the point is to push the GUI calls onto the main thread. All GUI calls, and no other calls.
I don't know what self.dv
is, but it doesn't look like a wx
GUI object (it has a Python-style set_data
method, not a wx
-style SetData
). Of course if it's shared between multiple threads, and it's not self-synchronized, you almost certainly want a Lock
or other sync object protecting it. But you don't need, or want, CallAfter
here.
That pd.Close
, on the other hand, is a GUI call. (Well, it would be if you remembered the parentheses.) You definitely need CallAfter
for that.
So:
def set_data(self,data):
"""set the data on the ObjectListView"""
pd = gui.ProgressDialog("Setting Data..")
def set_thread():
data = takes_time()
with self.dv_lock:
self.dv.set_data(data)
wx.CallAfter(pd.Close)
#opens a thread that sets the data
t = Thread(target = set_thread,name="SetDataThread")
t.start()
You also asked about CallLater
. You use this when you want some code to be run on the main thread after X milliseconds, not as soon as possible. While you could use CallLater(0, pd.Close)
in place of CallAfter(pd.Close)
, there's no good reason to do so.
Upvotes: 2