GUI, restez vivants (sans update)!

 

La page GUI, restez vivants ! montre comment éviter que l'interface reste bloquée jusqu'à la fin d'un long calcul.

La technique proposée consiste essentiellement à intercaler des commandes update a certains endroits stratégiques. De cette façon, l'interface est actualisée par update et reste vivante.

Cette approche requiert aussi que l'on considère dans la boucle de calcul la possibilité que l'utilisateur aît changé les paramètres avant la fin du calcul, de sorte de pouvoir avorter un calcul devenu innécessaire.

Cette page propose une autre technique qui ne requiert pas update.


La technique (http://wiki.tcl.tk/1526) consiste à décomposer le calcul en petites étapes, et assurer que chaque étape schedule (?) la suivante. Le code alternatif est

  # ---------------
  # parameters
  # ---------------

  set radius 64
  set px 0.25
  set py -0.25
  set ::TRACE 0
  set next {}

  # ---------------
  # procs
  # ---------------

    # compute ignoredArg
    #
    # Lancer le calcul avec des nouveaux paramètres
    # ---------------
   proc compute - {
      global px py currentpx currentpy next radius
      if {$px == $currentpx && $py == $currentpy} {
	  if {$::TRACE} { puts "current" }
	  return
      }
      set currentpx $px
      set currentpy $py
      if {$::TRACE} { puts "compute $px $py" }

      # Cancel any scheduled steps for old values
      after cancel $next
      set next {}

      # Schedule the first step
      set dx [expr {$px * $radius}]
      set dy [expr {$py * $radius}]
      set step [list compute_step -$radius -$radius $dx $dy]
      schedule $step
  }

  proc schedule cmd {
      variable next [after 0 $cmd]
  }

  proc compute_step {x y dx dy} {
      global radius radius2
      if {$x == $radius} {
	  if {[incr y] == $radius} return;# finished!
	  set x [expr {-$radius}]
      }

      set r [expr {($x * $x + $y * $y) / $radius2}]
      if {$r <= 1} {
	  set r [expr {(($x - $dx) * ($x - $dx) + ($y - $dy) * ($y - $dy))/ $radius2}]
	  set c [expr {127 + int(128 * (1 - $r))}]
	  if {$c < 0} { set c 0 }
	  _img_ put [format \#%2.2x%2.2x%2.2x $c $c $c] \
	      -to [expr {$x + $radius}] [expr {$y + $radius}]
      }

      # Schedule the next step
      set step [list compute_step [incr x] $y $dx $dy]
      variable next [after idle [list schedule $step]]
  }

  # ---------------
  # packages
  # ---------------

  package require Tk
  package require Img

  # ---------------
  # interface
  # ---------------
  wm title . "Keeping alive"
  set diameter [expr {$radius * 2}]
  set radius2 [expr {double($radius * $radius)}]
  set pi/2 [expr {asin(1)}]
  set currentpx ""
  set currentpy ""
    # image
    # ---------------
  image create photo _img_ \
    -width $diameter -height $diameter
    # canvas
    # ---------------
  canvas .c -bd 0 -highlightt 0 \
    -width $diameter -height $diameter
  .c create image 0 0 -anchor nw -image _img_
  pack .c
    # label frame
    # ---------------
  labelframe .lf -text "glint relative position" -relief groove
    # scales
    # ---------------
  label .lf.lgx -text \nx
  scale .lf.sgx -orient horizontal -variable ::px \
    -from -1.0 -to 1.0 -resolution 0.05 -length 200
  label .lf.lgy -text \ny
  scale .lf.sgy -orient horizontal -variable ::py \
    -from -1.0 -to 1.0 -resolution 0.05 -length 200
    # place & display
    # ---------------
  pack .lf
  grid .lf.lgx .lf.sgx
  grid .lf.lgy .lf.sgy
  update
    # events
    # ---------------
  .lf.sgx config -command compute
  .lf.sgy config -command compute
  wm protocol . WM_DELETE_WINDOW exit

  # ---------------
  # go
  # ---------------

  compute -

Objectif

  1. Le widget doit détecter le changement de paramètres et doit arrêter aussitôt son calcul.
  2. Les widgets annexes ne doivent pas être bloqués pendant le calcul.

Questions/Réponses (mimiquées de GUI, restez vivants !, pour contraster les réponses)

Quand les paramètres changent, la procédure compute élimine la prochaine étape de la queue d'évènements, de sorte qu'elle ne sera pas invoquée.

--

A la fin de chaque étape de calcul, la prochaîne étape est mise dans la queue d'évènements par after idle, pour être executée après tout autre évènement qui s'y trouve déja. Il n'y a pas d'appel récursif à la boucle des évènements, donc pas besoin d'une estimation de profondeur.

--

after idle est nécessaire pour que la prochaine étape ne soit invoquée qu'après la fin des autre évènements dans la queue. after 0 parce qu'il est dangereux d'avoir des after idle imbriqués.


/Cette alternative a certains avantages, et certains désavantages:


Discussion

ulis

(En tout cas, un grand merci pour l'explication de cette alternative)

MS Oui - et non. La différence est que dans l'autre version update court la boucle, tout en attendant que ce soit fini. Si la boucle relance compute, et celui-ci relance la boucle, ...

Dans cette version, compute_step se met dans la queue d'évènements et retourne immédiatement. Pas de récursion possible. Il ne peut jamais avoir qu'un seul évènement dans la queue avec cette construction (sans compter ceux qui proviennent de la GUI, bien sur).

ulis Je retente ma chance :

Si j'ai bon, cette méthode est bien plus sûre que toute méthode avec update. Bien entendu, il faut pouvoir l'utiliser. Et effectivement elle oblige à programmer les boucles sans les instructions de boucles habituelles.

MS Les deux commandes mettent une tâche dans la queue, et malheuresement une seule commande ne marche pas bien: si [after 0 ...], on ne donne pas de temps d'exéution aux idle events - et il n'est pas bon d'imbriquer les [after idle ...] ("At present it is not safe for an idle callback to reschedule itself continuously. This will interact badly with certain features of Tk that attempt to wait for all idle callbacks to complete.")

Il n'y a pas d'exécution en parallèle, un script n'est jamais interrompu par des évènements (s'il l'était, il n'y aurait pas de problème à résoudre avec la réponsivité de la GUI). Donc le fait qu'une occurence est terminée avant le début de la suivante est garanti, indépendemment de [after idle] ...