Help | Site Map
Connecting Tech Pros Worldwide
 
 
LinkBack Thread Tools
  #1  
Old August 10th, 2005, 03:25 PM
perchef
Guest
 
Posts: n/a
Default wxPython and threads again

Hi,

I have several files to download and a GUI to update. I know this is a
frequently asked question but i can't find an appropriate solution.
My Downloader extends threading.Thread and update a wx.Gauge in GUI
during the process.

for src in urls:
downloader = Downloader( src, destination, GUI )
downloader.start()

#work with the downloaded files...

If i don't use a downloader.join() in this for loop, I launch several
threads at the same time and so my wx.Gauge is bouncing up and down.
If i do add the downloader.join() my GUI is no more updated ( in fact,
nothing appears, it's frozen )
How can I wait the end of the thread and also be able to update the GUI
?
( I have to wait, otherwise I will work uncompleted files )

Any way to work around it?
Thanks.

  #2  
Old August 10th, 2005, 06:15 PM
David E. Konerding DSD staff
Guest
 
Posts: n/a
Default Re: wxPython and threads again

In article <1123683323.056092.92800@o13g2000cwo.googlegroups. com>, perchef wrote:[color=blue]
> Hi,
>
> I have several files to download and a GUI to update. I know this is a
> frequently asked question but i can't find an appropriate solution.
> My Downloader extends threading.Thread and update a wx.Gauge in GUI
> during the process.
>
> for src in urls:
> downloader = Downloader( src, destination, GUI )
> downloader.start()
>
> #work with the downloaded files...
>
> If i don't use a downloader.join() in this for loop, I launch several
> threads at the same time and so my wx.Gauge is bouncing up and down.
> If i do add the downloader.join() my GUI is no more updated ( in fact,
> nothing appears, it's frozen )
> How can I wait the end of the thread and also be able to update the GUI[/color]

Well, the constraints are this: the main thread is running the wx main loop,
and thus, cannot block by calling join on the downloader thread. Further, calling
wx from a thread other than the one running the event loop is deep voodoo and should typically
be avoided.

You need another way to pass completion information between the downloader
thread and the main thread; the simplest way is to define a custom wx
Event, and wxPostEvent from the downloader thread when it completes (
and when the gauge should be updated). wxPostEvent is safe to call from non-eventloop threads.
The main thread's wx event loop just spins, properly updating all other
parts of the GUI, and receiving events from the downloader thread.

ANother approach is to have a thread-safe Queue and have the main thread/event loop
poll the queue with queue.get_nowait() periodically (typically 0.1-1 sec).
The downloader thread shares the queue object and puts data structures (typically
class instances, strings, or ints) that indicate status updates.

The easiest approach, though, is to use the threadedselectreactor in Twisted (you need
to check the HEAD branch out with subversion, because that reactor isn't included in any releases).
With threadedselectreactor, it's easy to incorporate both the GUI event loop and the twisted reactor.
Twisted already includes lots of code for doing asynchronous callback-style IO for
IO bound processes like downloading. Further, you don't even think in an explicitly threaded way-
createing a whole thread just to manage a download process which is motly IO and a little bookkeeping is
silly. Twisted's approach just makes a lot more sense and simplifies the code too.

Dave
  #3  
Old August 10th, 2005, 07:45 PM
Bryan Olson
Guest
 
Posts: n/a
Default Re: wxPython and threads again

David E. Konerding DSD staff wrote:
[...][color=blue]
> You need another way to pass completion information between the[/color]
downloader[color=blue]
> thread and the main thread; the simplest way is to define a custom wx
> Event, and wxPostEvent from the downloader thread when it completes (
> and when the gauge should be updated). wxPostEvent is safe to call[/color]
from non-eventloop threads.[color=blue]
> The main thread's wx event loop just spins, properly updating all other
> parts of the GUI, and receiving events from the downloader thread.
>
> ANother approach is to have a thread-safe Queue and have the main[/color]
thread/event loop[color=blue]
> poll the queue with queue.get_nowait() periodically (typically 0.1-1[/color]
sec).[color=blue]
> The downloader thread shares the queue object and puts data[/color]
structures (typically[color=blue]
> class instances, strings, or ints) that indicate status updates.[/color]

The way-cool things to transmit, in either the queue or the
event data, are tuples of:

(func, args, kwargs)

The so-called-'main' thread gets these, and blindly calls
func(*args, **kwargs). Since only the main thread can safely
update the GUI, other threads pass GUI-updating functions to be
called by the main thread.

The technique is beautifully general. The worker thread does
it's long, blocking operations independently, and when it needs
to update the GUI it sends the main thread a quick, non-blocking
function.

In wxPython, custom events can carry arbitrary data, so the easy
thing to do is just pass the (func, args, kwargs) across with
wxPostEvent (or so I've read; I'm not a wxPython user).

TkInter has no equivalent to wxPostEvent. Contrary to popular
belief, TkInter's event_generate is not thread-safe. The usual
TkInter solution is a queue, which the main thread periodically
polls via the 'after' function.

[color=blue]
> The easiest approach, though, is to use the threadedselectreactor in[/color]
Twisted (you need[color=blue]
> to check the HEAD branch out with subversion, because that reactor[/color]
isn't included in any releases).[color=blue]
> With threadedselectreactor, it's easy to incorporate both the GUI[/color]
event loop and the twisted reactor.[color=blue]
> Twisted already includes lots of code for doing asynchronous[/color]
callback-style IO for[color=blue]
> IO bound processes like downloading. Further, you don't even think[/color]
in an explicitly threaded way-[color=blue]
> createing a whole thread just to manage a download process which is[/color]
motly IO and a little bookkeeping is[color=blue]
> silly. Twisted's approach just makes a lot more sense and simplifies[/color]
the code too.

I couldn't disagree more about that being easier and simplifying
the code. "Creating a whole thread" is trivial.


--
--Bryan
  #4  
Old August 10th, 2005, 08:45 PM
Peter Hansen
Guest
 
Posts: n/a
Default Re: wxPython and threads again

David E. Konerding DSD staff wrote:[color=blue]
> Further, calling wx from a thread other than the one running the event
> loop is deep voodoo and should typically be avoided.[/color]

"Typically"? Let's just say "always" and maybe use the phrase "certain
to corrupt wx and crash the app" instead of "deep voodoo". :-) At least
that way the OP won't waste time experimenting...
[color=blue]
> You need another way to pass completion information between the downloader
> thread and the main thread; the simplest way is to define a custom wx
> Event, and wxPostEvent from the downloader thread when it completes (
> and when the gauge should be updated). wxPostEvent is safe to call from non-eventloop threads.
> The main thread's wx event loop just spins, properly updating all other
> parts of the GUI, and receiving events from the downloader thread.[/color]

Even simpler for some purposes is wx.CallAfter(), which provides the
asynchronous performance of wxPostEvent with the "beautifully general"
approach of passing callables through a Queue which Bryan Olson
described in his post. (That is, you don't need to do polling with a
non-blocking get() on the Queue this way.)

-Peter
  #5  
Old August 10th, 2005, 11:15 PM
perchef
Guest
 
Posts: n/a
Default Re: wxPython and threads again

thanks for all these advices.
I think a custom event will be the way to go.
for the moment I use something _really_ ugly : a mix between GUI,
threads and recursive fonctions.

  #6  
Old August 11th, 2005, 01:05 AM
Bryan Olson
Guest
 
Posts: n/a
Default Re: wxPython and threads again

Peter Hansen wrote:[color=blue]
> David E. Konerding DSD staff wrote:[color=green]
>> Further, calling wx from a thread other than the one running the
>> event loop is deep voodoo and should typically be avoided.[/color]
>
> "Typically"? Let's just say "always" and maybe use the phrase "certain
> to corrupt wx and crash the app" instead of "deep voodoo". :-) At least
> that way the OP won't waste time experimenting...[/color]

Come to think of it, wouldn't it be a good idea for a GUI
toolkit to to do something like:

import thread

# ...

def WhateverToolKitInitFunction(*args):
global _thread_of_record
_thread_of_record = thread.get_ident()
# ...


def check_thread():
if thread.get_ident() != _thread_of_record:
raise RuntimeError('Attempt to update GUI from foreign
thread.')


And then begin each non-thread-safe function like:


def SomeUpdateFunction(*args):
check_thread()
# ...


[...][color=blue]
> Even simpler for some purposes is wx.CallAfter(),[/color]

Ah, Nice. Same method under the hood, but hides the complexity.


--
--Bryan
  #7  
Old August 11th, 2005, 02:25 AM
Christopher Subich
Guest
 
Posts: n/a
Default Re: wxPython and threads again

David E. Konerding DSD staff wrote:[color=blue]
> The easiest approach, though, is to use the threadedselectreactor in Twisted (you need
> to check the HEAD branch out with subversion, because that reactor isn't included in any releases).
> With threadedselectreactor, it's easy to incorporate both the GUI event loop and the twisted reactor.
> Twisted already includes lots of code for doing asynchronous callback-style IO for
> IO bound processes like downloading. Further, you don't even think in an explicitly threaded way-
> createing a whole thread just to manage a download process which is motly IO and a little bookkeeping is
> silly. Twisted's approach just makes a lot more sense and simplifies the code too.[/color]

Or, don't use threadedselectreactor, but instead just use normal
threading and reactor.callFromThread.
  #8  
Old August 11th, 2005, 05:05 PM
David E. Konerding DSD staff
Guest
 
Posts: n/a
Default Re: wxPython and threads again

On 2005-08-10, Bryan Olson <fakeaddress@nowhere.org> wrote:[color=blue][color=green]
> > The easiest approach, though, is to use the threadedselectreactor in[/color]
> Twisted (you need[color=green]
> > to check the HEAD branch out with subversion, because that reactor[/color]
> isn't included in any releases).[color=green]
> > With threadedselectreactor, it's easy to incorporate both the GUI[/color]
> event loop and the twisted reactor.[color=green]
> > Twisted already includes lots of code for doing asynchronous[/color]
> callback-style IO for[color=green]
> > IO bound processes like downloading. Further, you don't even think[/color]
> in an explicitly threaded way-[color=green]
> > createing a whole thread just to manage a download process which is[/color]
> motly IO and a little bookkeeping is[color=green]
> > silly. Twisted's approach just makes a lot more sense and simplifies[/color]
> the code too.
>
> I couldn't disagree more about that being easier and simplifying
> the code. "Creating a whole thread" is trivial.
>
>[/color]

I've done both styles. Actually, I greatly prefer the single threaded approach now; conceptually,
the threaded approach is very simple, but your program ends up getting complex because the data-passing
infrastructure. And you end up structuring your logic in somewhat more convoluted ways. And
most people have such a hard time dealing with data synch between threads...

Dave
  #9  
Old August 11th, 2005, 05:15 PM
David E. Konerding DSD staff
Guest
 
Posts: n/a
Default Re: wxPython and threads again

On 2005-08-10, Peter Hansen <peter@engcorp.com> wrote:[color=blue]
> David E. Konerding DSD staff wrote:[color=green]
>> Further, calling wx from a thread other than the one running the event
> > loop is deep voodoo and should typically be avoided.[/color]
>
> "Typically"? Let's just say "always" and maybe use the phrase "certain
> to corrupt wx and crash the app" instead of "deep voodoo". :-) At least
> that way the OP won't waste time experimenting...[/color]

Not really certain:
http://wxwidgets.org/manuals/2.6.1/w...hreadfunctions

This strongly suggests you can arbitrarily grab the wx GUI lock and call GUI
functions from any thread. It's still voodoo. But we're adults here, and practicing
voodoo isn't proscribed.
[color=blue]
>[color=green]
>> You need another way to pass completion information between the downloader
>> thread and the main thread; the simplest way is to define a custom wx
>> Event, and wxPostEvent from the downloader thread when it completes (
>> and when the gauge should be updated). wxPostEvent is safe to call from non-eventloop threads.
>> The main thread's wx event loop just spins, properly updating all other
>> parts of the GUI, and receiving events from the downloader thread.[/color]
>
> Even simpler for some purposes is wx.CallAfter(), which provides the
> asynchronous performance of wxPostEvent with the "beautifully general"
> approach of passing callables through a Queue which Bryan Olson
> described in his post. (That is, you don't need to do polling with a
> non-blocking get() on the Queue this way.)[/color]

Very good point. I wasn't aware CallAfter had those semantics; I always used it from
the main thread. But won't wx.CallAfter cause a bit of a delay since it will wait until
all pending events are processed and only handle the function afterwards, thus inducing
an extra event loop cycle/redraw?

Dave
  #10  
Old August 11th, 2005, 07:55 PM
Peter Hansen
Guest
 
Posts: n/a
Default Re: wxPython and threads again

David E. Konerding DSD staff wrote:[color=blue]
> http://wxwidgets.org/manuals/2.6.1/w...hreadfunctions
>
> This strongly suggests you can arbitrarily grab the wx GUI lock and call GUI
> functions from any thread. It's still voodoo. But we're adults here, and practicing
> voodoo isn't proscribed.[/color]

Oh, very nice... for my automatic testing requirements anyway. I did
not know those existed...

I agree using this in real code is probably not wise, and you're right
that it is best described as "voodoo". :)
[color=blue]
> But won't wx.CallAfter cause a bit of a delay since it will wait until
> all pending events are processed and only handle the function afterwards,
> thus inducing an extra event loop cycle/redraw?[/color]

Since it's built on PostEvent, it will do whatever happens with other
stuff that is PostEvent-ed, I suppose. Your description is probably
accurate...

-Peter
 

Bookmarks

Thread Tools

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are Off
[IMG] code is Off
HTML code is Off
Trackbacks are On
Pingbacks are On
Refbacks are On

What is Bytes?

We are a network of experts and professionals in IT and software development that help one another with answers to tough questions and share insights. Get the best answers to your questions from over network members.
Post your question now . . .
It's fast and it's free

Popular Articles