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

Hello!

I'm using Tk::ExecuteCommand to run a command (some other script) from GUI and see its output (using perl 5.6 under Windows)

The problem is:
if I start a script which runs longer time, the GUI is not responding... for a normal user this is very confusing - people think the applicaiton has frozen.

Is there any way to do the Tk GUI be still responding?

Thanks for your advices!
dzon
  • Comment on Tk::ExecuteCommand - GUI not responding by executing longer running script

Replies are listed 'Best First'.
Re: Tk::ExecuteCommand - GUI not responding by executing longer running script
by zentara (Cardinal) on Feb 04, 2008 at 12:42 UTC
    You have to show the code, and be aware that Perl5.61 is ancient, and that Windows presents some problems, so we can't say what is happening just based on your question.

    In general, you can do a $mw->update often to keep the gui responsive, but I'm inclined to believe that you are running into an odd problem because you are running on Windows, or the command you are executing is very fickle. See How to skip to next Command line


    I'm not really a human, but I play one on earth. Cogito ergo sum a bum
      I use perl 5.6.1 under Windows because it needs to run in this environment ... I also tried it in perl 5.8.8 but it didn't print the output at all

      The important part of code of the application:
      use Tk; require Tk::ExecuteCommand; $top = MainWindow->new; $executecommand = $top->ExecuteCommand( -height => 10, -scrollbars => +'se', bg => 'white')->pack( -expand => 'yes', -fill => 'both' ); $executecommand->terse_gui; $executecommand->bell; $executecommand->update; $b_savelog = $top->Button( -text => "Start script", -width => 12, -command => sub { $executecommand->configure( -command => "sleep.pl"); $executecommand->execute_command; } )->pack( -side => 'right' ); MainLoop;
      and the sleep.pl for testing:
      print "Starting..."; sleep 10; print "end\n";
      ...in the real application I start script which finds specified files in filesystem, parses them and then starts some other scripts ... which takes several minutes

      I was thinking about using threads, but I know about some problem with Tk and threads. I think a way could be to change somehow the Tk::ExecuteCommand (http://search.cpan.org/~lusol/tkjuke-1.0.6/Tk/ExecuteCommand.pm) - but I don't how yet...
Re: Tk::ExecuteCommand - GUI not responding by executing longer running script
by zentara (Cardinal) on Feb 04, 2008 at 20:26 UTC
    I think the problem is that Tk::ExecuteCommand uses a tk fileevent to watch the pipe that feedbacks from the command. Tk::fillevent on windows, only works on sockets, it will mess up on pipes.

    So your options are either to switch to the IPC::Run module, or use threads.

    Since windows use threads anyways, you may as well go with threads. Threads and Tk can work very well together with some precautions. See Tk-with-worker-threads for a starting point. The main precautions are to create your threads first, before any Tk code is called. Keep Tk code only in the main thread. Use shared variables to communicate between threads.


    I'm not really a human, but I play one on earth. Cogito ergo sum a bum
      for my application would be really better, if I could just change the Tk::ExecuteCommand and not use threads

      I think the change is needed somewhere in these 2 functions in Tk::ExecuteCommand (http://search.cpan.org/src/LUSOL/tkjuke-1.0.6/Tk/ExecuteCommand.pm):
      sub _read_stdout { # Called when input is available for the output window. Also chec +ks # to see if the user has clicked Cancel. my($self) = @_; if ($self->{-finish}) { $self->kill_command; } else { my $h = $self->{-handle}; if ( sysread $h, $_, 4096 ) { my $t = $self->Subwidget('text'); $t->insert('end', $_); $t->yview('end'); } else { $self->{-finish} = 1; } } } # end _read_stdout sub execute_command { # Execute the command and capture stdout/stderr. my($self) = @_; my $h = IO::Handle->new; die "IO::Handle->new failed." unless defined $h; $self->{-handle} = $h; $self->{-pid} = open $h, $self->{-command} . ' 2>&1 |'; if (not defined $self->{-pid}) { $self->Subwidget('text')->insert('end', "'" . $self->{-command} . "' : $!\n"); $self->kill_command; return; } $h->autoflush(1); $self->fileevent($h, 'readable' => [\&_read_stdout, $self]); my $doit = $self->Subwidget('doit'); $doit->configure( -text => 'Cancel', -relief => 'raised', -state => 'normal', -command => [\&kill_command, $self], ); my $doit_bg = ($doit->configure(-background))[3]; $self->_flash_doit(-background => $doit_bg, qw/cyan 500/); $self->waitVariable(\$self->{-finish}); } # end execute_command
      ... is it actually possible to solve my problem so easily - just with changing this module (replace somehow the fileevent, which could be the problem)?
        Hi, see Perl/Tk App and Interprocess Communication for how someone else solved it on win32. You can "roll-your-own" Tk::ExecuteCommand by just popping a text widget in a toplevel windoow, and feed results into from a thread. You will need a timer to constantly pull (read the pipe) instead of a fileevent. You setup the timer to run very fast, like 5 ms, and in the callback, read the pipe. Using the Win32::Pipe module may also help. You can use the timer to read a shared variable from a thread, as an alternative using threads.

        I'm not really a human, but I play one on earth. Cogito ergo sum a bum