« Especto Dispatcher.BeginInvoke ! »

On a parfois l’impression que certaines commandes du framework tiennent plus de la magie que de la raison.
Mais à Poudlard, on n’aime pas ce genre d’impression.
Soit c’est de la magie, soit ce n’en est pas.
Nous allons donc étudier dans cet article le cas fameux du « Dispatcher » qui pour beaucoup tient plus de la magie noir trollesque que de l’informatique d’aujourd’hui.

0bf38853-593d-43a0-ba22-5850e094d592

Dispatcher à quoi ça sert ?

La classe Dispatcher nous permet de transplaner d’un Thread background vers le Thread UI (d’accéder) par le biais de sa méthode BeginInvoke. Vous ne le savez peut être pas mais le transplanage est interdit entre ces threads sous peine de gros problème d’exception (« désartibulage »). Mais l’invocation de BeginInvoke permet de contrer cet interdit comme on le verra dans cet article !

Apparate
(ceci est la meilleur image de transplanage que j’ai pu trouvé)

Pourquoi transplaner entre Threads ?

Lorsque l’on clique sur un bouton de notre app, l’événement Click exécute du code dans une tache qui se nomme « Thread UI ». Ce thread, unique dans notre application, a pour vocation d’afficher l’ensemble de l’interface graphique ainsi que gérer les entrées utilisateurs et autres événements. C’est donc un thread extrêmement important !
Si l’on effectue un gros traitement sur ce thread, notre application se figera (« Stupefix ») durant son exécution ce qui est rarement l’objet recherché.
On préfère donc effectuer ces traitements dans des Threads en tâche de fond. Il existe plusieurs façons d’invoquer ces threads. En voici une relativement spectaculaire :

Le snippet « Lumos Task » (snippet qui n’existe qu’à Poudlard) devrait invoquer un thread comme suit :

await Task.Run( () => 
{
	// Mon gros traitement dans le thread
}
) ;

Si, dans ce thread, on appelle un élément géré par le ThreadUI (par exemple modifier le texte d’un Textblock), nous auront droit à une belle exception de type «Cross-Access thread not allowed».
On vous l’a dit ! Il est interdit de transplaner entre thread !

harry-potter-movie-jk-rowling-writing

Heureusement il existe une solution : Le Dispatcher.BeginInvoke !

Commencer l’Invocation (BeginInvoke)

On invoquera le BeginInvoke dans notre Task de la manière suivante :

await Task.Run( () => 
{
	// Mon gros traitement dans le thread
	Dispatcher.BeginInvoke( () =>
{
	// ici toutes les modifications à effectuer dans l’UI
		This.TextBlock.Text = « Hello Hermione ! » ;
	}) ;
}
) ;

Et là, comme par magie, la modification s’effectuera sur le ThreadUI sans pour autant générer une exception.
Mais comment cela est-il possible ?

Comment fonctionne le BeginInvoke par Mrs Granger (qui a levé la main)

HermioneL

« On peut faire passer des objets à travers les threads en les stockant dans un objet commun (une pile par exemple fera l’affaire).
Comme un delegate est basiquement un pointeur sur une méthode mais également un objet on peut donc stocker des méthodes dans cette pile.
C’est ce que fait le BeginInvoke en stockant son paramètre « Action a » dans une Stack (car le type Action hérite de delegate).

Public void BeginInvoke(Action a)
{
	lock( this.Stack)
        {
	   this.Stack.Push( a ) ;
        }
}

La méthode pourra être retirée par le ThreadUI par le biais d’un timer (DispatcherTimer) toutes les X millisecondes puis exécutée ensuite !

            Stack<Action> stack = new Stack<Action>();

            DispatcherTimer t = new DispatcherTimer();
            t.Interval = TimeSpan.FromMilliseconds(100);
            t.Tick += (object sender, EventArgs e) =>
            {
                Action action = null;
                lock(stack)
                {
		   If( stack.Count > 0 )
                   {
                    		action = stack.Pop() as Action;	
		   }
                }
	        // execution de l’action
                If( action != null ) action();
            };
	    t.Start() ;

La méthode est donc passée d’un thread à l’autre. Et son exécution n’est pas immédiat car liée au Timer. »

Merci Mrs Granger. Allez vous asseoir à coté de votre ami rouquin.
Notez que la Stack est verrouillée (instruction lock) de chaque coté des threads afin de s’assurer qu’un Push ne s’effectue pas en même temps qu’un Pop ce qui rendrait la Stack instable.

Le mot de la fin au Professeur Dumbledor !

« Nomme toujours les choses par leur nom. La peur d’un nom ne fait qu’accroître la peur de la chose elle-même »
(cette citation n’a aucun lien avec cette article mais l’agrémente de bien belle façon)

dumbledore

BeginInvoke stocke ses actions dans une pile et le threadUI les dépile dans un simple Timer. Cela n’a rien de magique et vous permettra de ne plus avoir peur des exceptions commençant par « Cross-Thread » !

Tout le monde a compris ?

harry-potter-and-the-sorcerers-stone-poster-7
(Ce n’est pas grave, ami de Mrs Granger)

One Response to « Especto Dispatcher.BeginInvoke ! »

  1.  

    Superbe article ! Merci ! :)

leave your comment