Hey all, especially Mattia,

I'm working my way through threads in Wx and I'm looking into using
threads to implement background queue processing. The user adds stuff
to a queue, which is a part of the app object, and it processes a loop
on the back end. Basically, I want them to be able to add to that
queue while it's going, without having to pause it.

I'm about to go to bed before delving into doing the real code, so I
figured I should throw this up to see if I'm approaching things right,
and see if there are any responses when I wake up.

Basically, in simple form, I'm thinking this is the right approach,
and I am about to try it. However, threads are tricky so even if it
works, if someone sees this and screams NO THAT'S WRONG I need to
listen. So, am I getting the approach right here?

package Polymorph;

use threads;
use threads::shared;
use Wx(':everything'); # I despise using 'qw' for one thing no matter
how many things everywhere do it as an example
use vars ('@ISA');
@ISA = ('Wx::Frame');

sub new {
    @_[3, 4, 5, 6] = (Polymorph', [-1, -1], [760, 610],
wxMINIMIZE_BOX|wxMAXIMIZE_BOX|wxSYSTEM_MENU|wxCAPTION|wxCLOSE_BOX);
    my $self = shift->SUPER::new(@_);

    $self->{params} = {};
    $self->{queue} = &share([]);
    $self->{qWorkEVT} = Wx::NewEventType;
    &share($self->{qWorkEVT});

    Wx::Event::EVT_COMMAND($self, -1, $self->{qWorkEVT}, \&wip_queue_process);

    # all the other GUI setup stuff goes here, including buttons which
trigger the events for subs below
}

sub add_to_queue { # add current to queue button pressed
    my $self = shift;
    my $e = shift;

    $self->{queue} ||= &share([]);

    my $q = &share({});
    for my $k (keys %{$self->{params}}) {
        $q->{$k} = $self->{params}->{$k};
    }
    $q->{status} = 'queued';
    push @{$self->{queue}}, $q;
}

sub process_queue { # start processing queue button pressed
    my $self = shift;
    my $e = shift;
    @_ = (); # do I need to do this if I shift in the vars? PBP is
full of crap on this one, IMO. I always shift in $self, and $e is easy
to do it with too.

    $self->{qthr} = threads->create(
        sub {
            my $pct_done : shared = 0;
            &share($pct_done);

            while (my $q = (grep $_->{status} eq 'queued',
@{$self->{queue}})[0]) {
                # not a for loop, in case the queue changes in the meanwhile
                warn "processing queue item $q->{outputFile}\n";
#warning while developing, since most of this will be quiet instead
                # do stuff to process the batch entry
                $q->{status} = 'done';
                $pct_done = scalar @{$self->{queue}} / (scalar(grep
$_->{status} ne 'queued', @{$self->{queue}}) * 100);
                my $wip = new Wx::PlThreadEvent(-1, $self->{qWorkEVT},
$pct_done);
                Wx::PostEvent($self, $wip);
            }

            $pct_done = 100;
            my $qdone = new Wx::PlThreadEvent(-1, $self->{qWorkEVT}, $pct_done);
            Wx::PostEvent($self, $qdone);
        }
    );
}

sub wip_queue_process {
    my $self = shift;
    my $e = shift;

    my $pct_done = $e->GetData;
    # Update a gauge, maybe, or something... print to the status bar. Whatever.

    if ($pct_done == 100) {
        $self->finish_queue_process($e);
    }
}

sub finish_queue_process {
    my $self = shift;
    my $e = shift;
    warn "queue processing done.\n";
    $self->{qthr}->join; # the examples don't show this anywhere, but
it's bad to not do this, right? Zombies, brain-eating, all that...
                               # And isn't this the perfect time to catch it?
}


package main;

unless (caller) {
    local *Wx::App::OnInit = sub {1};

    my $app = Wx::App->new();
    my $window = Polymorph->new();

    $app->SetTopWindow($window);
    $window->Show(1);
    $app->MainLoop();
}



BTW!
On a side note, I noticed something funny looking in the docs.

In the Wx threading pod, it says:

"Sending events from worker threads

Wx::PlThreadEvent can be used to communicate between worker and GUI
threads. The event can carry a shared value between threads.

  my $DONE_EVENT : shared = Wx::NewEventType;

  sub work {
      # ... do some stuff
      my $progress = new Wx::PlThreadEvent( -1, $DONE_EVENT, $progress );
      Wx::PostEvent( $frame, $progress );

      # ... do stuff, create a shared $result value
      my $end = new Wx::PlThreadEvent( -1, $DONE_EVENT, $result );
      Wx::PostEvent( $frame, $end );
  }
The target of the event can be any Wx::EvtHandler"

However, there seems to be something wrong in that. Mainly in that
there's a '$progress' variable declared in a my, but being dependant
on already being defined in the same scope.

I'm thinking something like this is what was meant, instead:

"
Sending events from worker threads

Wx::PlThreadEvent can be used to communicate between worker and GUI
threads. The event can carry a shared value between threads.

  my $DONE_EVENT : shared = Wx::NewEventType;

  sub work {
      # ... do some stuff, generating a shared $progress variable
      my $progress_so_far = Wx::PlThreadEvent->new(-1, $DONE_EVENT, $progress);
      Wx::PostEvent( $frame, $progress_so_far );

      # ... do stuff, create a shared $result value
      my $end = Wx::PlThreadEvent->new( -1, $DONE_EVENT, $result );
      Wx::PostEvent( $frame, $end );
  }
The target of the event can be any Wx::EvtHandler
"

That avoids saying my $progress = new Wx::PlThreadEvent(-1,
$DONE_EVENT, $progress);
and thus avoids using $progress to define my $progress, because I'm
pretty sure that's not right. I mean, unless you are defining
$progress elsewhere.

-- 
Dodger

Reply via email to