Beefy Boxes and Bandwidth Generously Provided by pair Networks
Problems? Is your data what you think it is?
 
PerlMonks  

Win32::GUI and threads issue

by Garden Dwarf (Beadle)
on Jan 15, 2019 at 10:20 UTC ( [id://1228580]=perlquestion: print w/replies, xml ) Need Help??

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

Hello Monks!

I am trying to create a Win32 application (with Strawberry Perl (v5.14.4) on Win10) displaying graphic computations. In order to optimize the process, I want to divide the management of my virtual buffer into small pieces computed by individual threads, then compile the results and copy the virtual buffer on the screen.

My problem is the combination of Win32::GUI and threads (I have also tried forkmanager without success). Threads without Win32 is ok, Win32 without threads is ok, but using both is not. Here is a simple sample code to illustrate the issue (you can enable/disable the use of Win32 with the variable $use_win or change the amount of threads with the variable $t_amount):

#!/bin/perl use Win32::GUI(); use threads; use strict; use warnings; use Data::Dumper; my $use_win=1; # Create Win32 GUI interface (1) or not (0) my $t_amount=4; # Amount of threads to create my $textbox; my $win; my $draw; if($use_win){ # Initialize window $win=new Win32::GUI::Window( -left => 0, -top => 0, -width => 300, -height => 300, -name => "Window", -text => "Test", ); $win->InvalidateRect(1); $textbox=$win->AddTextfield( -name => "Output", -left => 5, -top => 5, -width => 275, -height => 255, -text => ""); # Start application $draw=$win->AddTimer('draw',1000); $win->Show(); Win32::GUI::Dialog(); }else{ draw_Timer(); } sub Window_Terminate{-1} sub draw_Timer{ my @threads; my @ret; my $c; my $d; # Assign range of computation to fork processes foreach $c(1..$t_amount){ $d=$c-1; push(@threads,threads->new(\&draw,($d*10),($d*10+10))); } foreach my $thread(@threads){ @ret=$thread->join; foreach my $data(@ret){ $use_win?$textbox->Append("|".$data):print"|".$data; } $use_win?$textbox->Append("\n"):print"\n"; } } sub draw{ my $begin=shift; my $end=shift; my @tbl; my $cpt; for($cpt=$begin;$cpt<$end;$cpt++){ push(@tbl,$cpt); } return(@tbl); }

Any help would be welcome. I have searched for previous posts without finding a solution. I also googled with no luck. Thanks in advance!

Replies are listed 'Best First'.
Re: Win32::GUI and threads issue
by marioroy (Prior) on Jan 17, 2019 at 07:25 UTC

    Hi Garden Dwarf,

    Based on your code, the following runs for me on a Windows 7 VM running Perl 5.26.1. Workers persist in the background. Two queues (inbound and outbound) are used. Due to lack of time, I apologize for not displaying to the terminal. The goal was seeing text display in the windows dialog. And yes, there you go. :)

    I added a state variable ($running) in the event a long running job so not to impact the main app.

    #!/bin/perl # Win32::GUI and threads issue # https://www.perlmonks.org/?node_id=1228580 use strict; use warnings; # One may run threads + MCE::Shared. This works very well. # Why MCE::Shared one might ask? Well, it handles serialization # of data objects automatically. E.g. passing array refs. use threads; use MCE::Shared; my $queue_in = MCE::Shared->queue(); my $queue_ou = MCE::Shared->queue(); my $t_amount = 3; # Amount of threads to create my $running = 0; my $textbox; my $win; # Important, spawn threads early before loading Win32::GUI. threads->new('producer') for 1..$t_amount; # Run the main app or consumer afterwards. consumer(); # Voila :) exit; sub consumer { require Win32::GUI; # Initialize window $win = new Win32::GUI::Window( -left => 0, -top => 0, -width => 300, -height => 300, -name => "Window", -text => "Test", ); $win->InvalidateRect(1); $textbox = $win->AddTextfield( -name => "Output", -left => 5, -top => 5, -width => 275, -height => 255, -text => "", -multiline => 1, ); # Start application (calls draw_Timer) $win->AddTimer('draw', 333); $win->Show(); Win32::GUI::Dialog(); } sub producer { $SIG{QUIT} = sub { threads->exit(); }; while ( defined ( my $next_args = $queue_in->dequeue ) ) { my ( $c, $begin, $end ) = @{ $next_args }; my @ret = compute($begin, $end); sleep 2; # simulate a long running process $queue_ou->enqueue([ $c, @ret ]); } } sub Window_Terminate { $queue_in->end(); $_->kill('QUIT') for threads->list; -1; } sub draw_Timer { # Enqueue range of computation to background threads if ( $running == 0 ) { $running = 1; foreach my $c (1..$t_amount) { my $d = $c - 1; $queue_in->enqueue([ $c, $d*10, $d*10+10 ]); } } # Obtain data once background threads have completed if ( $running == 1 && $queue_ou->pending == $t_amount ) { $running = 0; my @ordered_ret; foreach my $c (1..$t_amount) { my $ret = $queue_ou->dequeue(); my $c = shift(@{ $ret }) - 1; # array-based-zero $ordered_ret[$c] = ""; foreach my $data (@{ $ret }) { $ordered_ret[$c] .= "|".$data; } $ordered_ret[$c] .= "\n"; } $textbox->Append($_) for @ordered_ret; } } sub compute { my ($begin, $end) = (shift, shift); my (@tbl, $cpt); for ($cpt = $begin; $cpt < $end; $cpt++) { push @tbl, $cpt; } return @tbl; }

    Regards, Mario

      MCE::Hobo works as well. The following are the changes to the above solution. The API is similar to threads.

      13c13 < use threads; --- > use MCE::Hobo; 25c25 < threads->new('producer') for 1..$t_amount; --- > MCE::Hobo->new('producer') for 1..$t_amount; 67c67 < threads->exit(); --- > MCE::Hobo->exit(); 79c79 < $_->kill('QUIT') for threads->list; --- > $_->kill('QUIT') for MCE::Hobo->list;

        Hi Mario,

        I've implemented your solution into my main code, and it is working fine. Thanks a lot!

        After having proceeded to some benchs, I noticed that dequeuing slows down the process. I tried with different amounts of threads and the performances are good, but unfortunately not good enough to have a real-time image processing. I know, perl may not be the right choice for this but I wanted to give it a try.

        On the other hand, adding tasks to a queue can be optimized by adding all of them at once ($queue_in->enqueue(task1,task2,...)). Processing time is reduced significantly compared to enqueuing each task separately. The code would look like this:

        my @taskslist; foreach my $c (1..$t_amount) { my $d = $c - 1; push(@taskslist,[ $c, $d*10, $d*10+10 ]); } $queue_in->enqueue(@taskslist);

        Well, thanks again and if you have a suggestion of how to increase the performances (staying with Perl), it would be nice!

Re: Win32::GUI and threads issue
by dasgar (Priest) on Jan 15, 2019 at 15:59 UTC

    You say that you're trying to use Win32::GUI and threads. It sounds like something isn't behaving the way that you were anticipating, but you're not providing information on what it is that you believe is a problem. This is like asking a mechanic what's wrong with your car without providing details about the problem or asking a doctor to give you a medical diagnosis without giving any information on the medical issues that you are having. If you want to get some help, then I would suggest that you help yourself by providing more details on what you are having issues with.

    Are you sure that the code you posted is the code that you were testing? The line with sub Window_Terminate{-1} is missing a semicolon at the end, which could potentially prevent your code from running.

    I personally have used Win32::GUI and threads so I can definitely say that it is possible to use the combination. As I have already mentioned, since you're not providing information about what problem(s) you are having, it's going to be nearly impossible for others to be able to help.

      The code I've posted is supposed to issue numbers. The first thread provides the numbers 0 to 9 to the parent, the second thread 10 to 19, etc. When the parent has all results, he displays all of them (on Win32 interface or into the terminal, depending on the setup I explained above).

      When I use Win32, the application stops without issuing anything (no numbers, no errors). I didn't know where to start investigations, so I posted my questions here.

        So you say you've got logic error... Timer never starts threads never start display never updates? Simplify code
Re: Win32::GUI and threads issue
by Anonymous Monk on Jan 15, 2019 at 10:34 UTC
      Thanks for the link, but I'm a bit lost. In your example Tk is used, not Win32.
        The point he was trying to make is that the Win32::GUI module is NOT THREADSAFE. This is true of many modules that use global variables, which is usually true with GUI toolkits. There are ways around it, usually by creating your threads first, before invoking Win32::GUI. This has been discussed so many times that I will leave it up to you to search for solutions. Since Tk is the most widely used GUI toolkit with Perl, searching Google for "perl Tk thread safety" will yield all the discussions and solutions. Usually the solutions involve creating your threads first, before any GUI statements ( because threads are copies of the originating script), and not putting any GUI code in the threads. The idea is to keep any GUI variables out of the threads. A proper design keeps your GUI code confined to the original thread to handle the display, and a means of communicating information between the spawned threads and the display thread, usually thru a timer to update, or some sort of fileevent.

        I'm not really a human, but I play one on earth. ..... an animated JAPH

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://1228580]
Approved by Corion
Front-paged by Discipulus
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others admiring the Monastery: (3)
As of 2024-04-25 05:48 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found