ori
ori

Reputation: 379

using threads with wxPython correctly

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

Answers (1)

abarnert
abarnert

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

Related Questions