Anonymous Monk has asked for the wisdom of the Perl Monks concerning the following question:

Dearest Monks,

I'm creating a Win32::GUI application - it's a customised setup utility for a game modification. Part of the application downloads a small text file from a web server; this text file acts as an index, and stores web addresses of mirror servers containing the game mod files that have to be downloaded and installed on the user's computer. The application then looks for a small text file on each of the mirror servers contained in the index, basically to check that they're responsive and can actually be used by the user. I'm using LWP::UserAgent to achieve all of this and it works rather nicely. While this process is taking place, an animated throbber is shown on-screen with various status/progress messages telling the user what is happening, and a "Cancel" button in case the user wants to close the application while the process is running.

This is no problem if the index server and mirror servers are all functioning correctly, because the process takes a few seconds. However, if a couple of the servers are unavailable, it can lengthen the process considerably (maybe by 20 seconds or so) and during that time the GUI is unresponsive and appears to have frozen or crashed: large parts of the GUI, such as all graphics and some labels, appear grey, and repeated clicks will result in the standard Windows "Not Responding" window appearing. The GUI appears normal and becomes responsive again when the process has finished. I believe that this is because Win32::GUI::DoEvents() hasn't been called for a while. The trouble is, the application will be used by less-advanced computer users who might mistake this for an actual crash and close the application, then complain to me that it doesn't work. :)

I've tried a number of different methods to keep the GUI responding:

Part of the problem lies with the fact that I have to support almost every version of Windows with this application; it has to work with 98/ME/2000/XP/2003, so a lot of the nice XP/2K-only solutions are cruelly snatched away from me.

My question, simply, is this: is there any way I can run the process I described while keeping the GUI responsive at the same time?

Any help, advice or comments are most welcome. Thank you! :)

Replies are listed 'Best First'.
Re: Keeping a Win32::GUI interface responsive while executing other code?
by liverpole (Monsignor) on Sep 27, 2006 at 17:56 UTC
    I've recently been working on exactly the same kind of problem with my own programming project; that of how to best keep a GUI responsive, while at the same time handling LWP requests.  I can tell you what has worked very nicely for me, and let you evaluate whether it's a solution you wish to pursue or not.

    The basic strategy is to have two separate threads, a parent thread and a child thread.

    The parent thread handles all of the GUI interactions, and anything else which is reasonably "lightweight".  The child thread handles all of the LWP (ie. downloads from a webserver), and otherwise just sits and spins in a tight loop.  I use threads::shared to share a few essential data between the threads, including some "state " variables, and I use Thread::Queue to pass a variable number of items back from the child to the parent.

    Pictorially, here is a diagram of how the threads interact, given a variable "$state_var":

    Parent Child +----------+ +-------------------------+ +----------------------- +-+ |Start: | |Decides remote data is | |Detects value of 1; + | |$state_var|==>|needed; parent changes |==>|Attempts to fetch the + | |value = 0 | |the value from 0 to 1. | |data using LWP. + | +----------+ +-------------------------+ | + | ^ +-------------------------+ |Success: Child changes + | | |Detects the value of 2; | |the value from 1 to 2, + | | |Reads the shared queue to| |and writes the data bac +k| +===========|retrieve the data, change|<==|to the shared queue. + | | |the value from 2 to 0, | | + | | |and return to Start. | |Failure: Child changes + | | +-------------------------+ |the value from 1 back t +o| | |0 to indicate failure, + | +=========================================|and returns to Start. + | +----------------------- +-+

    This has proven very successful for me; the parent is the only thread which can change the state variable from 0 to 1; the child is the only thread which can change it from 1 to 0 (on an error), or from 1 to 2 (on success), and the parent is the only thread which can change it from 2 back to 0 (and at the same time, retrieve any data the child thread is writing).

    Prior to my implementing threads, the GUI was very non-responsive for often as many as 10-20 seconds at a time, if there was, for any reason, a delay in the remote data fetch.  Now, the GUI has no problems interacting; when data is delayed, it simply continues to wait until the state variable changes to indicate that it was successful, and that data is waiting to be fetched from the child (which, of course, happens very quickly).

    I've run the program so far under Windows XP and Linux, with equal success.  I would be interesting to hear whether similar success is possible under Windows 2000 (or other platforms), but I don't see any reason why it shouldn't be, as long as the version of ActiveState Perl was the most recent available.


    s''(q.S:$/9=(T1';s;(..)(..);$..=substr+crypt($1,$2),2,3;eg;print$..$/
Re: Keeping a Win32::GUI interface responsive while executing other code?
by ruoso (Curate) on Sep 28, 2006 at 09:25 UTC

    The keyword you want to search for is "Worker Thread". Basically it's what has been replied before, but you don't need to have a permanent thread for doing this jobs (as you may need to do more than one job at a time)...

    The idea, basically, is to start a "Worker Thread" for every potentially-time-consuming task and release the GUI thread (the main thread) immediatly. You probably wants to have a busy switch so you can tell your user that the program is doing something (like having a different cursor).

    daniel
Re: Keeping a Win32::GUI interface responsive while executing other code?
by eriam (Beadle) on Sep 28, 2006 at 09:40 UTC
    Dude what you need is called POE !
    Check it out on CPAN and have fun.
Re: Keeping a Win32::GUI interface responsive while executing other code?
by Outaspace (Scribe) on Sep 28, 2006 at 09:29 UTC
    Have you considered to put the reader in a different script and call this asynchrone from your app (as a different process). Then you can create a Timer to read the output of this script. Have used this kind to read the Perl Module List from my installation (take quite a while). Also I would recommend wxPerl (wxWidgets), but I dont know how much work you want to put in it (learning wxPerl takes a while, but it is worth it).

    Andre
Re: Keeping a Win32::GUI interface responsive while executing other code?
by Anonymous Monk on Sep 28, 2006 at 15:07 UTC
    Most generous thanks to everyone who has replied so far - I really appreciate it!

    Currently I'm working on a method heavily based on liverpole's and ruoso's suggestions, and it's working beautifully. The interface is constantly responsive and everything seems to work as it should.

    I think in the long term, eriam's solution is the most appropriate - POE seems like a pretty solid module that might be best suited for anything larger than the application I'm producing. I'll certainly keep POE in mind for any future GUI work that I need to do.

    Outaspace: I did try something along those lines, and while it worked brilliantly on Win2K/XP, it didn't perform very well on Win98/ME; in fact if I recall correctly it didn't want to work at all. I did take a look at wxPerl some time ago and you're right, it does take a lot of effort, but it would be the better long-term solution. I definitely wouldn't do anything bigger than this with Win32::GUI.

    Thanks again for everyones' responses! :)
      By the way you can use POE with wxPerl thanks to Mattia and Mike's work on POE-Loop-Wx.

      Combine POE and wxPerl with a gui designer (visualWx) and it's awesome !

      Sure it does need some efforts but hey it's worth considering the pleasure. eriam