q!

Hi everyone,

This is a long message describing what I've learned about Win32::GUI,
threads, and the DBI (DBD::mysql in my case).

First of all, I want to thank the authors and contributors to this
great Win32::GUI module.  I've been using the module for a couple
of weeks now on a side programming job I have.  In the spirit of
giving back, I'd like to share a solution that I've come across
regarding a multi-threaded Win32-GUI app with a worker thread that
makes DB connections, does other work, and updates the GUI with its
progress.  I hope this can help someone.

=== The Problem ===

I want a multi-threaded GUI application because I have long queries
that I need to run and I still want my GUI to be responsive to the
user.  With a single threaded application, while the thread is waiting
on a query to complete, the GUI freezes up and becomes unresponsive.

To solve this issue, I first tried to use the Win32::GUI::ThreadUtils
module. (http://www.robmay.me.uk/win32gui/ BTW, thanks to Mr. Rob May.
I'm not using ThreadUtils but I am using your really cool SplashScreen
module.)  I can get the ThreadUtils examples to work, but when I start
mixing in DB connections, that's when things start to get hairy.

When using threads and the MySQL DBD, I seem to have issues.  The
connection works fine when instantiated, but when the script exits,
there is a garbage collection error: Free to wrong pool during global
destruction. (Resulting in one of those horrible Microsoft Send Error
Report popups.)  This does not make a professional looking
application.

Here's an example of the threaded DBD::mysql problem:

<perlcode>

use strict;
use threads;
use DBI;

my $thr = threads->new(\&worker);
$thr->join;
print "Uh-oh\n";

sub worker {
  my $dbi = DBI->connect( 'dbi:mysql:test', 'user', 'pass' );
  return 0;
}

</perlcode>


=== My Solution ===

Strangely enough, fork() works where perl threads don't.  I don't
know the details of why this is.  I say strangely because it is my
understanding that fork is emulated on Windows by using perl threads.
(See http://search.cpan.org/~nwclark/perl-5.8.8/pod/perlfork.pod).
I've found that a fork()ed pseudo-process that has its own DB
connection doesn't die with the same garbage collection error as
threads do when exiting.

So, my setup is to have a worker fork()ed pseudo-process that handles
all DB connections.  The worker is pre-forked (after the GUI is set
up) so that there is no delay when the user makes a request.  The
GUI thread communicates with the worker via a one-way pipe.

It is my understanding that this works as the the fork()ed worker
pseudo-process gets a copy of all the GUI handles and makes its own
DB handles.  GUI handles can be shared among threads, but DB handles
can't be shared.

Compatibility:  In my application, I'm also using the GUI Loft visual
editor to create my GUI and the Win32::GUI::SplashScreen module for
a quick and easy splash screen.  Both tools seem to work fine with
this method of preforking a worker process.  Actually, I think the
splash screen is necessary because there is a noticable delay when
calling fork() (or at least on my machine there is).

See http://www.bahnhof.se/~johanl/perl/Loft/ for more info on GUI
Loft.

One final note on the "Attempt to free unreferenced scalar" errors.
I've noticed that when I create my own Win32::GUI objects "by hand",
I get "Attempt to free unreferenced scalar" errors on script
termination.  This is also noted in the examples from
Win32::GUI::ThreadUtils.  However, if I use Win32::GUI::Loft to create
my GUI objects, I don't get any errors on script termination.  I'm
not sure why this is.

I've included an example below.

=== My Configuration Details ===

Win32::GUI - 1.03
DBI        - 1.53
DBD::mysql - 3.0002
perl       - v5.8.4 built for MSWin32-x86-multi-thread
             (ActiveState binary build 810).
The modules were installed from the ActiveState PPM2 Repository.
MySQL server - 5.0


Well, I hope this can help someone.  I'd love to hear any questions
or comments.

Thanks,
Charles Alderman
charlie-win32gui at aldermania dot com
!;

####  Example  ####
## Obviously, this example is written for windows.  I'm running XP.
## In this example, there is a GUI thread (parent) and a worker thread
## (child).  The worker thread handles all DB connections to a MySQL
## database.
##
## Note: I just say thread, although each thread is actualy a
## pseudo-process that has been forked.  The terminology may need
## some work.

use strict;
use warnings;

use DBI;
use Win32::GUI ();

my $parent;   # Indirect filehandle for the GUI thread

sub main {

  my $win = Win32::GUI::Window->new(
    -name  => 'MainWindow',
    -title => 'Forking Exmple',
          -pos   => [100,100],
          -size  => [300,300],
  );

  $win->AddTextfield(
          -name => 'tfResults',
          -multiline => 1,
          -vscroll => 1,
          -autovscroll => 1,
          -pos => [10,10],
          -size => [280,200],
  );

  $win->AddButton(
    -name => 'btQuery',
    -text => 'Run Query',
          -pos  => [10, 220],
          -height => 25,
  );

  ########################################################
  ## Set up worker process... communicate through a pipe
  ## the worker thread eval's anything passed from the GUI thread.
  ##
  pipe my $child, $parent or die "Can't create pipe: $!";
  my $pid = fork;
  die "Can't fork: $!" unless defined $pid;

  # Child...
  if ($pid == 0) {

    my $dbh = DBI->connect( 'dbi:mysql:test_db', 'user', 'pass' );

    close $parent;
    while (<$child>) {
      print "$_\n";

      my $results = eval $_;
      next unless $results;

      for my $row ( @$results ) {
        my $str = join ', ', @$row;
        $str .= "\r\n";  # \r\n necessary for the textfield
        $win->tfResults->Append( $str );
      }
    }
    exit(0);
  }

  # Back to parent...
  # Make sure to set the pipe to auto-flush
  close $child;
  my $stdout = select $parent;
  $|++;
  select $stdout;
  ########################################################

  $win->Show();
  Win32::GUI::Dialog();
  $win->DESTROY;
}


sub MainWindow_Terminate {
  close $parent;  # This is necessary to clean up the worker thread
  wait;
  return(-1);
}

sub btQuery_Click {
  print $parent q{
    $dbh->selectall_arrayref('SELECT * FROM my_table');
  };
}

# Start up...
eval { main() };
Win32::GUI::MessageBox(0, "Error: $@", "DOH") if $@;




Reply via email to