Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why are waitVariable calls not independent even if different variable ref is used?

I'm new to Tk and I'd like to know whether the issue in question is a normal Tk behavior or not.

In short: I have a Perl/Tk (Tk version 804.028) script which uses two Tk::ExecuteCommand (v1.6) widgets. Such objects have an execute_command method which uses the defined fileevent callback to read the executed command's stdout and returns after it is finished. It is solved with the usage of waitVariable. But it seems if two ExecuteCommand started together they returns only when the slower returns. I may expect that the faster returns immediately, when finished.

I made a small test Perl/Tk script to demonstrate the problem:

#!/usr/bin/perl

use strict;
use warnings;
use Tk;
use Tk::ROText;

my $MAIN = new MainWindow -title => "TEST";
my $text = $MAIN->Scrolled('ROText')->pack(qw/-expand 1 -fill both/);

sub pr { # Write into ROText widget
  $text->insert('end', join '', @_); $text->yview('end');
}
pr "Tk version ", Tk->VERSION, "\n";

my @v = (100, 200);
sub doo { # Button callback
  my ($rv, $txt) = @_;
  pr "B4 wait: $txt, ref=$rv, val=", $$rv, "\n";
  $MAIN->waitVariable($rv);
  pr "Aft wait: $txt, ref=$rv, val=", $$rv, "\n";
}

$MAIN->Button(-text => 'Do 0', -command => [\&doo, \$v[0], "Do 0" ]
    )->pack(qw/-expand 1 -side left -fill both/);
$MAIN->Button(-text => 'Stop 0', -command => [sub {++$v[0]; pr "Stop 0\n";} ]
    )->pack(qw/-expand 1 -side left -fill both/);
$MAIN->Button(-text => 'Do 1', -command => [\&doo, \$v[1], "Do 1" ]
    )->pack(qw/-expand 1 -side left -fill both/);
$MAIN->Button(-text => 'Stop 1', -command => [sub {++$v[1]; pr "Stop 1\n";} ]
    )->pack(qw/-expand 1 -side left -fill both/);

MainLoop();

This draws an ROText widget and 4 Buttons ([Do 0][Stop 0][Do 1][Stop 1]) (see attached picture). Clicking on a Do button, calls the function doo, which waits until the assigned scalar has changed. The variables are changed when a Stop button is pressed.

If the buttons are pressed in the [Do 0][Stop 0][Do 1][Stop 1] order the output seems to be ok (see lines 2-7). But if the "tasks" started in parallel then both callback finishes only if both are stopped. So pressing the Buttons in [Do 0][Do 1][Stop 0][Stop 1] (see lines 8-13) gives a weird result (see picture).

enter image description here

My expectation for the second test was that the first callback function returns immediately after pressing the first Stop Button. So I think output should be:

B4 wait: Do 0, ref=SCALAR(0x9970560), val=101
B4 wait: Do 1, ref=SCALAR(0x9970bfc), val=201
Stop 0
Aft wait: Do 0, ref=SCALAR(0x9970560), val=102
Stop 1
Aft wait: Do 1, ref=SCALAR(0x9970bfc), val=202

It runs on a Linux machine.

Am I missing something? Thanks in advance!

UPDATE

To bypass this waitVariable issue I rewrote this widget to use callbacks instead (thanks to Tantalus!). Now execute_command returns immediately. There are two callbacks, one for Cancel, one for Finish. Now the caller is informed through these callbacks. Anyway I read somewhere (I cannot find the source now) that waiting in a callback for long is not a good idea in Tk. The new solution is compliant with this.

Thank for your help!

like image 944
TrueY Avatar asked Jan 05 '17 17:01

TrueY


2 Answers

$widget->waitVariable(\$name)

$widget->waitVisibility

$widget->waitWindow

The tk wait methods wait for one of several things to happen, then it returns without taking any other actions. The return value is always an empty string. waitVariable expects a reference to a perl variable and the command waits for that variable to be modified. This form is typically used to wait for a user to finish interacting with a dialog which sets the variable as part (possibly final) part of the interaction. waitVisibility waits for a change in $widget's visibility state (as indicated by the arrival of a VisibilityNotify event). This form is typically used to wait for a newly-created window to appear on the screen before taking some action. waitWindow waits for $widget to be destroyed. This form is typically used to wait for a user to finish interacting with a dialog box before using the result of that interaction. Note that creating and destroying the window each time a dialog is required makes code modular but imposes overhead which can be avoided by withdrawing the window instead and using waitVisibility.

While the tk wait methods are waiting they processes events in the normal fashion, so the application will continue to respond to user interactions. If an event handler invokes tkwait again, the nested call to tkwait must complete before the outer call can complete.

Emphasis mine.

like image 183
ikegami Avatar answered Nov 12 '22 12:11

ikegami


You should not be waiting in an event loop. What is happening is that you're producing a callstack that involves being inside the previous wait loop. For example, if you add use Carp; at the top, and then change your pr function like this:

sub pr { # Write into ROText widget
  $text->insert('end', Carp::longmess(join '', @_)); $text->yview('end');
}

then you'll see the waitVariable showing up - we can't get back there until we're back in that loop, and you're ending up with loops inside of loops.

To do what you want to do without inverting everything into events, you may want to try Coro which can invert the events like this.

Also, in modern perls, qw does not imply parenthesis, so your pack calls need parenthesis around the qw/.../ lists.

like image 2
Tanktalus Avatar answered Nov 12 '22 12:11

Tanktalus