Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations Chris Miller on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Fixing a Perl/Tk GUI that's unresponsive when running long processes.

Graphical User Interface (GUI)

Fixing a Perl/Tk GUI that's unresponsive when running long processes.

by  icrf  Posted    (Edited  )
The problem happens when you're trying to execute a long running process from a GUI. The application seems to just hang, or sometimes even crash, even though it's still working, doing whatever you told it. Sometimes you're updating information in some widget while it's running, but the button you clicked to start it hasn't even popped back up, much less displayed your data. This doesn't look very professional, and your users are coming back with complaints.

What has to be understood is how a Tk application runs (or really, any graphical/event-based app). When you're writing the initial code, nothing actually happens until you hit MainLoop() and the Tk event loop begins. When an event comes in, like you resized the window or clicked a button, an event is triggered and MainLoop() catches and handles it, redraws the button to show you clicked it, calls whatever subroutine you might have bound to the event.

In this example, your long running process is just a counter, which is bound to a label widget. You just want to see it count and be able to stop it. Since MainLoop() called this long running sub, it isn't in control anymore. It can't handle updating the text in the label, resizing of the window, showing action on the buttons, etc. So, you have to call it explicitly with [link http://search.cpan.org/~ni-s/Tk/pod/pTk/DoOneEvent.pod]DoOneEvent()[/link].

That's essentially all MainLoop() does, it just waits for events to come and handles them. So you're essentially making your own little MainLoop(), but it's doing other things, too (like counting). You can pass different event types you want to handle to DoOneEvent() if you only want to handle specific kinds, or are getting strange behavior. You have to explicitly import them when you [tt]use Tk qw/TK_FILE_EVENTS/;[/tt] if you want them. Passing nothing just uses the default TK_ALL_EVENTS, which works well enough most of the time. See the documentation linked above for details on the available event types and their use.

Code:
use Tk;

my $count = 0;
my $loop = 0;
my $mw = new MainWindow();

$mw->Label(-textvariable => \$count)->pack;
$mw->Button(-text => 'Start', -command => \&long_job)
   ->pack(-side => 'left');
$mw->Button(-text => 'Stop',  -command => sub { $loop = 0 } )
   ->pack(-side => 'left');

MainLoop();

sub long_job
{
        $loop = 1;
        while($loop)
        {
                $count++;
                DoOneEvent();
        }
}

Most long running processes are some kind of loop, so you just have to insert a call to DoOneEvent() somewhere in the loop to handle all the events that might come up during its run. Above, when you click "Stop", it catches the event and executes the anonymous sub bound to it to set $loop to 0. So after it returns, the while condition in long_job no longer passes and the counting stops. All the while, the events to change the value of what's displayed in the text box is handled, too, as well as any resizing your might do.

Also of note (especially with long running processes), last time I checked, Tk was not thread safe. Trying to have multiple threads work with Tk data just brings heaps of problems. I don't know if a simple fork()ed process has the same issues, but I'd imagine so.

If you have any comments, corrections, or additions, I encourage you to send some feedback and I'll get back to you.
Register to rate this FAQ  : BAD 1 2 3 4 5 6 7 8 9 10 GOOD
Please Note: 1 is Bad, 10 is Good :-)

Part and Inventory Search

Back
Top