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

I'm learning WxPerl as a replacement for some of my Tk apps and have a question. In several of my apps I currently have smaller Perl programs (turned into *.exe files via PerlApp) that I launch and comunicate with. I've decided to pull these smaller programs into my new WxPerl app in the form of sub routines. I need to have the ability to interact with the subs. I assume I need to use threads as I discovered in the sample I found below. How can I stop a thread on demand? Do I need to create some sort of listener or watchdog thread that kills the worker thread?

use threads; use threads::shared; use Thread::Queue; use strict; use Wx; package MyApp; use base 'Wx::App'; sub OnInit { my $self = shift; my $frame = MyFrame->new(); $frame->Show(1); $self->SetTopWindow($frame); return 1; } package MyFrame; use base 'Wx::Frame'; use Wx qw(wxTE_MULTILINE wxVERTICAL wxID_DEFAULT); use Wx::Event qw(EVT_COMMAND EVT_CLOSE EVT_BUTTON); my $done_event : shared = Wx::NewEventType; sub new { my $class = shift; my $self = $class->SUPER::new(undef, -1, "The title"); $self->{text} = Wx::TextCtrl->new($self, -1, "", [-1,-1], [300, 30 +0], wxTE_MULTILINE); $self->{button} = Wx::Button->new($self, -1, "&Get Sites"); $self->{button_stop} = Wx::Button->new($self, -1, "Stop"); my $sizer = Wx::BoxSizer->new(wxVERTICAL); $sizer->Add($self->{text}); $sizer->Add($self->{button}); $sizer->Add($self->{button_stop}); $self->SetSizer($sizer); $self->{text}->AppendText("test\n"); EVT_COMMAND($self, -1, $done_event, \&done); EVT_BUTTON($self,$self->{button}, \&on_button ); EVT_BUTTON($self,$self->{button_stop}, \&test ); my $sendqueue = Thread::Queue->new(); my $worker = threads->create(\&worker, $self, $sendqueue ); $self->{queue} = $sendqueue; $self->{worker} = $worker; $self->Fit; return $self; } sub test { my ($self, $event) = @_; $self->{queue}->enqueue('STOP'); } sub on_button { my ($self, $event) = @_; $self->{queue}->enqueue('GO'); } sub done { my ($self, $event) = @_; my $text = $event->GetData; $self->{text}->AppendText("$text\n"); } sub worker { my($handler, $queue) = @_; use LWP::Simple; while (my $message = $queue->dequeue ) { return 1 if $message eq 'STOP'; my @sites = qw( http://www.google.com/ http://www.microsoft.com/ http://www.yahoo.com/ http://www.cpan.org/ http://www.perl.org/ ); # For testing purposes for(0..10) { for(0 .. $#sites) { my $page = get($sites[$_]); my ($title1) = $page =~ /<title[^>]*>\s*(.+?)<\/title[^>]*>/gs +i; my $title : shared = $title1; print "$title\n"; my $thread_event = Wx::PlThreadEvent->new(-1, $done_event, $ti +tle); Wx::PostEvent($handler, $thread_event); } } } } package main; MyApp->new->MainLoop;

Thanks, -Paul

Replies are listed 'Best First'.
Re: Wx subs and threads
by zentara (Cardinal) on Jun 27, 2008 at 12:23 UTC
    How can I stop a thread on demand? Do I need to create some sort of listener or watchdog thread that kills the worker thread?

    It's the same as all other threaded Perl apps. You can setup a shared variable, which you constantly test in the thread, telling it to return. Or if you use the latest threads, it has

    # Send a signal to a thread $thr->kill(’SIGUSR1’);
    which would be useful for stopping a long running executable.

    I'm not really a human, but I play one on earth CandyGram for Mongo
      Perfect...included with the threads module is a great example script that explains it all. Thanks for pointing me in the right direction.
Re: Wx subs and threads
by Anonymous Monk on Jun 27, 2008 at 16:06 UTC
    I modified the sample code for threads to simulate what I want. It works perfect via CLI but when I incorporate it into my Wx App the process launches and the app locks up. Any ideas? I've included my sample code below:
    #!/usr/bin/perl use strict; use warnings; use threads 1.39; use threads::shared; use Thread::Queue; ### Global Variables ### # Maximum working threads my $MAX_THREADS = 1; # Flag to inform all threads that application is terminating my $TERM :shared = 0; # Prevents double detach attempts my $DETACHING :shared; ### Signal Handling ### # Gracefully terminate application on ^C # or command line 'kill' $SIG{'INT'} = $SIG{'TERM'} = sub { print(">>> Terminating <<<\n"); $TERM = 1; }; DeployThread(); sub DeployThread { # Manage the thread pool until signalled to terminate while (! $TERM) { # Keep max threads running for (my $needed = $MAX_THREADS - threads->list(); $needed && ! $TERM; $needed--) { # New thread threads->create('worker'); } } # Detach and kill any remaining threads foreach my $thr (threads->list()) { lock($DETACHING); $thr->detach() if ! $thr->is_detached(); print "Killing " . $thr->tid() . "\n"; $thr->kill('KILL'); } print("Done\n"); } # A worker thread sub worker { ### INITIALIZE ### # My thread ID my $tid = threads->tid(); printf("Working -> %3d\n", $tid); ### WORK ### # Do some work while monitoring $TERM my $sleep = 5 + int(rand(10)); my $i = 0; while (1) { print "Working ...\n"; sleep 1; $i ++; last if $i == 5; } ### DONE ### $TERM = 1; # Remove signal handler $SIG{'KILL'} = sub {}; # Tell user we're done printf(" %3d <- Finished\n", $tid); # Detach and terminate lock($DETACHING); threads->detach() if ! threads->is_detached(); threads->exit(); }
      when I incorporate it into my Wx App the process launches and the app locks up.

      Most gui interfaces are not threadsafe, so you need to launch the thread BEFORE any gui code is used, and don't try to access gui widgets from the thread code block. Launching the thread before any gui code is written usally requires some shared variables to tell the thread code to start and stop. For a simple example of a sleeping thread, see PerlTk on a thread.... Of course, the while loop in the thread can become more complex, having nested loops for a working state, and a sleeping state. While in the sleeping state, it should regularily check for a shared variable telling it to run, and if you want to reuse the thread, it should check while it's running to drop back into the sleep state. Search groups.google.com for "perl tk sleeping thread" for more examples. Sorry I don't do much Wx.

      Make your design to reuse threads, unless it's a 1-shot deal, because the thread ref-count cleanup is not perfect, and you will gain memory everytime you spawn a thread. Always try to reuse widgets and threads, and you will be happier in the long run.


      I'm not really a human, but I play one on earth CandyGram for Mongo
        That makes sense but I'm a little confused as how I can start/stop my thread if it's created prior to my GUI. I've attached my (hack at this) below. Thanks for any help!
        use threads; use threads::shared; use Wx; use Thread::Queue; use strict; # Maximum working threads my $MAX_THREADS = 1; # Flag to inform all threads that application is terminating my $TERM :shared = 0; # Prevents double detach attempts my $DETACHING :shared; package MyApp; use base 'Wx::App'; sub OnInit { my $self = shift; my $frame = MyFrame->new(); $frame->Show(1); $self->SetTopWindow($frame); return 1; } package MyFrame; use base 'Wx::Frame'; use Wx qw(wxTE_MULTILINE wxVERTICAL wxID_DEFAULT); use Wx::Event qw(EVT_COMMAND EVT_CLOSE EVT_BUTTON); my $done_event : shared = Wx::NewEventType; sub new { my $class = shift; my $self = $class->SUPER::new(undef, -1, "The title"); $self->{text} = Wx::TextCtrl->new($self, -1, "", [-1,-1], [180, 10 +0], wxTE_MULTILINE); $self->{button_start} = Wx::Button->new($self, -1, "&Start Test"); $self->{button_stop} = Wx::Button->new($self, -1, "&Stop Test"); my $sizer = Wx::BoxSizer->new(wxVERTICAL); $sizer->Add($self->{text}); $sizer->Add($self->{button_start}); $sizer->Add($self->{button_stop}); $self->SetSizer($sizer); $self->{text}->AppendText("test\n"); EVT_BUTTON($self,$self->{button_start}, \&DeployTestThread ); EVT_BUTTON($self,$self->{button_stop}, \&StopTestThread ); return $self; } sub DeployTestThread { # Manage the thread pool until signalled to terminate while (! $TERM) { # Keep max threads running for (my $needed = $MAX_THREADS - threads->list(); $needed && ! $TERM; $needed--) { # Create New thread my $thread = threads->create('ExecuteTest'); } } # Detach and kill any remaining threads foreach my $thr (threads->list()) { lock($DETACHING); $thr->detach() if ! $thr->is_detached(); print "Killing " . $thr->tid() . "\n"; $thr->kill('KILL'); } print("Done\n"); } sub StopTestThread { $TERM = 1; } sub ExecuteTest { my($handler, $queue) = @_; use LWP::Simple; # For Testing purposes for (1..10) { my @sites = qw( http://www.google.com/ http://www.microsoft.com/ http://www.yahoo.com/ http://www.cpan.org/ http://www.perl.org/ ); for(0 .. $#sites) { my $page = get($sites[$_]); my ($title1) = $page =~ /<title[^>]*>\s*(.+?)<\/title[^>]* +>/gsi; my $title : shared = $title1; print "$title\n"; my $thread_event = Wx::PlThreadEvent->new(-1, $done_event, + $title); Wx::PostEvent($handler, $thread_event); } } } package main; MyApp->new->MainLoop;