PhysiciSteve has asked for the wisdom of the Perl Monks concerning the following question:
Hi monks, I'm new to the concept of threading and am having trouble with a seemingly simple task.
I have a working Perl/Tk script. What I want is a subroutine to periodically do something (query something from the terminal, do a calculation). I want this to happen in the background, once a minute, so that I can still have access to the GUI and its functions (this is the part I'm stuck on). The end result of that calculation would be to simply update a textvariable in a Label in the GUI once a minute.
Any help would be appreciated!!!
Re: Perl/Tk threading and/or cron job?
by liverpole (Monsignor) on Sep 20, 2011 at 13:55 UTC
|
Hi PhysiciSteve,
It may help us more if you show what you've tried.
Having said that, I'll give you a very simple example that may do more-or-less what you need. It starts a worker thread which calculates the localtime every minute, and passes that to the main thread through the shared variable $ltime. It's important to keep the Tk code separate from the thread code (because Tk is not thread-safe), which is why the textvariable '$lbl_label' was kept separate from $ltime, and only assigned from $ltime from within the Tk idle loop update_gui().
You can, of course, change sleep 60; to a smaller interval to see it update more often:
#!/usr/bin/perl -w
# Libraries
use warnings;
use strict;
use threads;
use threads::shared;
use Tk;
use Tk::Font;
# Shared variables
my $ltime : shared = 0;
# Globals
my $lbl_label = "Started";
# Main Program
my $thread = threads->create(\&worker_thread);
$thread->detach;
create_gui();
# Subroutines
sub worker_thread {
while (1) {
$ltime = localtime(time());
print "Debug> In worker_thread(): Updated localtime to '$ltim
+e'\n";
sleep 60;
}
}
sub create_gui {
my $mw = new MainWindow(-title => 'Thread example');
my $fr = $mw->Frame->pack(-expand => 1, -fill => 'both');
my $fnt = $mw->Font(-family => 'arial', -size => '12');
my $lbl = $fr->Label(-textvar => \$lbl_label, -background => '#ffe
+fb5');
my $btn = $fr->Button(-text => 'Exit (ESC)', -background => 'cyan'
+);
$btn->configure(-command => sub { exit }, -font => $fnt);
$lbl->configure(-font => $fnt);
$lbl->pack(-side => 'left');
$btn->pack(-side => 'right');
$mw->bind("<Escape>" => sub { $btn->invoke });
$mw->repeat(1000 => \&update_gui);
MainLoop;
}
sub update_gui {
printf "Debug> In update_gui(): %s\n", time();
$lbl_label = $ltime;
}
s''(q.S:$/9=(T1';s;(..)(..);$..=substr+crypt($1,$2),2,3;eg;print$..$/
| [reply] [d/l] [select] |
|
use strict;
use warnings;
use threads;
use threads::shared;
use Tk;
use Tk::Font;
use Tk::ProgressBar;
use MCE;
# Shared variables
my $percentage_completed : shared = 0.0;
# Globals
my $percentage_label = 0.0;
my $download_label = "Completed...$percentage_completed%";
# Main Program
my $mce_thread = threads->create(\&run_mce);
run_gui();
# Subroutines
sub indicator {
my ($current_line, $total_lines) = @_;
$percentage_completed = sprintf '%.1f', $current_line * 100 / $tot
+al_lines;
}
sub run_mce {
my $n=10;
my $mce = MCE->new(
max_workers => 1,
init_relay => 0,
sequence => [ 0, $n ],
chunk_size => 1,
user_func => sub {
my ($mce, $i, $chunk_id) = @_;
MCE::relay { indicator($i, $n) };
sleep 1;
}
)->run;
}
sub run_gui {
my $mw = MainWindow->new(-title => 'Downloading...');
my $message = $mw->Message(
-textvariable => \$download_label,
-width => 130,
-border => 2
)->pack(-side => 'top');
$mw->geometry('350x100');
$mw->resizable(1,0);
my $progress = $mw->ProgressBar(
-width => 15,
-from => 0,
-to => 100,
-blocks => 50, #more block more smooth
-gap => 1, #use 0 to get solid bar else use 1
-colors => [ 0, '#104E8B' ],
-variable => \$percentage_label
)->pack(-fill => 'x');
my $button = $mw->Button(
-text => 'Cancel (ESC)',
-command => sub {
($percentage_completed < 100.0)
? MCE::Signal::stop_and_exit('TERM')
: $mw->destroy;
}
)->pack(-side => 'right');
$mw->bind('<Escape>' => sub { $button->invoke });
$mw->repeat(100 => \&update_gui);
MainLoop;
}
sub update_gui {
$percentage_label = $percentage_completed;
$download_label = "Completed...$percentage_completed%";
}
| [reply] [d/l] |
Re: Perl/Tk threading and/or cron job?
by keszler (Priest) on Sep 20, 2011 at 13:53 UTC
|
| [reply] [d/l] |
Re: Perl/Tk threading and/or cron job?
by zentara (Archbishop) on Sep 20, 2011 at 15:30 UTC
|
There are a bunch of ways to do this, and using a thread has many ways of returning the data. Here is a way using a reusable thread, returning the data thru shared variables. I've included an Entry widget for you to get terminal input from.
#!/usr/bin/perl
use warnings;
use strict;
use threads;
use threads::shared;
# uses a reusable thread concept
# enter any value in lower entry and the sin() will be
# computed in the top entry
my $val = 0; # textvariable return variable
#create thread before any tk code is called
my $data_in:shared = '';
my $data_return:shared = '';
my $go_control:shared = 0;
my $die_control:shared = 0;
my $thr = threads->new(\&excecute);
use Tk;
my $mw = MainWindow->new();
# catch window close button to clean up threads
$mw->protocol('WM_DELETE_WINDOW' => sub { &clean_exit });
$mw->fontCreate('big',
-weight=>'bold',
-size=> 14
);
my $label = $mw->Label(
-bg=> 'white',
-width => 50,
-font => 'big',
-textvariable => \$val )->pack();
my $txt = $mw->Entry(-bg=>'lemonchiffon', -font => 'big')->pack(qw/-fi
+ll x -pady 5/);
$mw ->bind('<Any-Enter>' => sub { $txt->Tk::focus });
$txt->bind('<Return>' => \&do_calc );
# initiate a calc whenever a return is hit in the entry widget
# you must read the shared var for the txtvar to update
# a timer to update label
my $timer = $mw->repeat(100,sub{ #every .1 second
$val = $data_return;
});
# a timer to initiate calculation
my $timer1 = $mw->repeat(5000,sub{ #every 5 seconds
&do_calc
});
MainLoop;
sub do_calc{
$data_in = $txt->get() || 0; # set a default value
#wake up thread
$go_control = 1;
}
sub clean_exit{
$timer->cancel;
$timer1->cancel;
my @running_threads = threads->list;
if (scalar(@running_threads) < 1){print "\nFinished\n";exit}
else{
$die_control = 1;
$thr->join;
exit;
}
}
sub excecute{
# thread code
while(1){
if($die_control){
print "thread finishing\n";
return}
#wait for $go_control
if($go_control){
if($die_control){
print "thread finishing\n";
return}
#do your calculation here
$data_return = sin( $data_in) .' '. time ;
print "$data_in $data_return\n";
#done calculating, so turn thread back to sleep
+
$go_control = 0;
}else{
select(undef,undef,undef,.25); }# sleep until awakened for next
+ command
}
return;
}
| [reply] [d/l] |
Re: Perl/Tk threading and/or cron job?
by PhysiciSteve (Initiate) on Sep 20, 2011 at 14:46 UTC
|
THANKS Keszler and liverpole!!!!!!
TK::After worked like a dream, exactly what I wanted. I first tried liverpoles solution which worked to a point, but I'm guessing because the loop never ends, the variable I want updated would not update in the GUI. I will investigate this further.
Thanks again everyone | [reply] |
Re: Perl/Tk threading and/or cron job?
by PhysiciSteve (Initiate) on Sep 21, 2011 at 02:30 UTC
|
Thanks zentara and liverpole, I've got the threading to work now and I think I've got my head around it. I appreciate the very informative responses! | [reply] |
|
|