http://www.perlmonks.org?node_id=585533

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

Ommmmmm.

Pondering has lead me to write a PerlTk GUI for a process control. Since it not apparent how to add file descriptors to the event manager (as in X), I tried to put the GUI in one thread, and the Msg I/O in another.

Calling the GUI as a subroutine works just fine.

But as a thread, it fails:

...... #gui(); # works # below fails my $gui_thr = threads->create( \&gui ); my $res1 = $gui_thr->join(); ... sub gui { # mw contains frames 1,2 and 3 top to bottom $mw = MainWindow->new( -background=>"$stock_bg"); $mw->resizable( 0, 0 );

Correcting the error seems beyond my skill level.

8763dbc is not a hash at /usr/lib/perl5/Tk/MainWindow.pm line 55. Aborted

Replies are listed 'Best First'.
Re: PerlTk on a thread...
by liverpole (Monsignor) on Nov 22, 2006 at 15:36 UTC
    Hi Wiggins,

    Tk is not thread safe, so you should not expect to be able to manage a gui from two threads in the same process.

    However, that shouldn't stop you from doing ALL gui management from within one thread (preferrably the parent), and setting up a worker thread to handle otherwise potentially-blocking actions (such as LWP fetches).  For that, you may want to look at using threads::shared.

    To do this, construct the thread before setting up the gui.  I've created an example program for you which illustrates this:

    #!/usr/bin/perl -w # Always use 'strict' and 'warnings' use strict; use warnings; # Libraries use threads; use threads::shared; use Thread::Queue; use Tk; use Tk::ROText; # Globals my $rotext = 0; # The Read-only text widget my $n_lines_waiting: shared = 0; # Message passing between thre +ads my $p_queue = Thread::Queue->new(); # Construct message 'Queue' #################### ### Main program ### #################### # Startup worker thread my $gui_thr = threads->create(\&worker_thread); # Only *now* is it safe to construct the GUI, from the parent thread gui(); + ################### ### Subroutines ### ################### # This subroutine is ONLY called from the parent thread sub gui { my $mw = MainWindow->new(); my $top = $mw->Frame()->pack(-expand => 1, -fill => 'both'); my $bt = $top->Button(-bg => 'skyblue', -text => "Exit"); $bt->configure(-command => sub { $mw->destroy() }); $rotext = $top->ROText(-bg => 'white'); $rotext->pack(); $bt->pack(); $mw->repeat(1000 => \&main_loop); MainLoop; } sub main_loop { if ($n_lines_waiting) { fetch_worker_data(); } } sub fetch_worker_data { for (my $i = 0; $i < $n_lines_waiting; $i++) { my $line = $p_queue->dequeue(); $rotext->insert("end", "$line\n"); } $rotext->insert("end", "--- End of $n_lines_waiting line(s) ---\n" +); $rotext->see("end"); my $mw = $rotext->toplevel(); $mw->update(); $n_lines_waiting = 0; } # This subroutine is ONLY called in the worker thread sub worker_thread { while (1) { sleep 3; worker_simulate_data(); } } sub worker_simulate_data { my $nlines = int(rand(10)); ($nlines > 0) or return; my $timestamp = localtime(time); for (my $i = 0; $i < $nlines; $i++) { my $idx = $i + 1; my $line = "[$timestamp] Random line of text #$idx"; $p_queue->enqueue($line); } $n_lines_waiting = $nlines; }

    The subroutine worker_simulate_data is called by the worker_thread subroutine every 3 seconds, and creates from 0 to 9 lines of data, which are then queued using Thread::Queue.  Finally, the shared variable $n_lines_waiting is set to the number of lines waiting in the queue.

    The parent thread gets the signal of how many lines are waiting via $n_lines_waiting, pulls this number of lines out of the shared queue, and writes them to the ROText window $rotext.

    Note that the worker thread only writes new data if $n_lines_waiting is zero (otherwise there are lines waiting which haven't been read by the parent thread yet).

    Of course, you are free to do other operations within the main_loop, as well as modify worker_thread so that it uses real-life data rather than a simulation.


    s''(q.S:$/9=(T1';s;(..)(..);$..=substr+crypt($1,$2),2,3;eg;print$..$/
Re: PerlTk on a thread...
by zentara (Archbishop) on Nov 22, 2006 at 15:17 UTC
    Hi, you will run into glitches unless you let Tk be the main thread. There are some rules to follow to use Tk with threads safely.

    1. Keep Tk only in the main thread. So make Tk just display shared variables, collected from the worker threads.

    2. Create the worker threads before any Tk statements are used.

    3. Do not try to access Tk widgets from worker threads. Pass shared variables instead. Tk -textvariable options will not be seen across threads. You need a timer to read them.

    4. If a thread is long running, and you need to kill it, you need to send it to the end of it's code block before joining it, or a harmless error is issued about "threads exiting while others are running".

    5. You can share filehandles thru threads, by passing the fileno, see Re^3: Passing globs between threads, this is very handy. It means you can print to a filehandle in a thread, and read it in main, or vice-versa, using fileevent.

    Here is some code to show the basics.

    #!/usr/bin/perl use warnings; use strict; use threads; use threads::shared; # for shared vars ..... # declare, share then assign value my $ret; share $ret; $ret = 0; my $val = 0; #create thread before any tk code is called my $thr = threads->create( \&worker ); gui(); # tk code only in main sub gui { use Tk; my $mw = MainWindow->new(); $mw->resizable( 0, 0 ); my $label = $mw->Label( -width => 50, -textvariable => \$val )->pack(); my $timer; $mw->repeat(10,sub{ $val = $ret; }); MainLoop; } # no Tk code in thread sub worker { for(1..10){ print "$_\n"; $ret = $_; sleep 1; } $ret = 'thread done, ready to join'; print "$ret\n"; }

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