Quand on commence à développer une application en WPF, on est rapidement confronté à des problématiques de multi-threading. En effet, il suffit de faire un appel serveur un peu long pour être confronté à cette problématique. Le code le moins propre qui puisse être fait est celui-là :

IOrderService service = ServiceManager.GetService<IOrderService>();
var order = service.GetOrder();

Le problème avec ce petit bout de code est le suivant : si la méthode GetOrder met du temps à répondre (plusieurs secondes), cela va bloquer votre interface le temps de la réponse du serveur. En effet le traitement sera effectué dans le thread du dispatcher, qui sera donc bloqué par votre code et qui ne pourra plus rafraichir votre vue. Pour résoudre ce problème, on peut utiliser un thread. Tout les threads d’un même processus partagent les mêmes ressources. C’est à dire que depuis deux threads différents, nous sommes capable de modifier la même donnée. Prenons en exemple le code suivant :

public int main() 
{ 
   var i = 0; 
   var t1 = Task.Factory.StartNew(() => 
   { 
       i = 10; 
   }); 
   var t2 = Task.Factory.StartNew(() => 
   { 
       i = 20; 
   }); 
   t1.Wait(); 
   t2.Wait(); 
   Console.WriteLine(i); 
}

On modifie la valeur de la variable i depuis deux threads différents. Ça fonctionne correctement, mais un problème persiste : qu’elle valeur de la variable va être écrite dans la console ? Il suffit d’exécuter ce code quelques dizaines de fois pour s’apercevoir que les valeurs 10 et 20 apparaissent dans la console. En effet les deux threads sont lancés en parallèle au même moment et pourtant une incertitude persiste quand à l’ordre d’exécution. Dans un cas comme celui-ci, il est impossible de savoir à l’avance quel thread va s’exécuter en premier.

Revenons à notre problème d’appel serveur qui bloque notre application. Pour le résoudre, nous pouvons donc faire notre appel dans un thread et stocker le résultat dans les propriétés du ViewModel appelant. Cela va bien pour des propriétés simple comme des chaînes de caractères, des entiers ou même des nombres à virgule. Si vous tentez le même code avec un thread qui modifie une ObservableCollection qui est bindée sur une vue, une exception sera lancée car il est impossible de modifier une collection depuis un autre thread que le thread UI si elle est bindée sur une vue.

Il ne faut pas binder d’objet de type List dans une vue. En effet, si vous déchargez la vue, le binding ne sera pas totalement supprimé car la propriété est un objet qui n’implémente pas INotifyPropertyChanged ou INotifyCollectionChanged, cela est une source de fuite mémoire.

Pour pouvoir modifier une ObservableCollection depuis un autre thread, il est possible de faire de la réentrance dans le thread UI de deux façons différentes. Soit on poste une action à exécuter dès que le thread UI aura du temps de libre, soit on poste une action à réaliser dès que le thread UI à terminé l’action en cours. Il s’agit respectivement des méthodes BeginInvoke et Invoke.

Ces méthodes sont disponible sur l’objet Dispatcher. Voila un exemple d’utilisation :

private void onAction(object obj) 
{ 
     System.Windows.Application.Current.Dispatcher.BeginInvoke(() => 
     { 
         Ints.Add(2); 
     }); 
}

Dans ce code, on récupère le dispatcher de l’application, cela nous permet d’exécuter un traitement qui va rafraichir la vue. Attention à ne pas récupérer le dispatcher du thread courant comme cela :

System.Windows.Threading.Dispatcher.CurrentDispatcher

Vous avez maintenant tout en main pour utiliser les méthodes Invoke et BeginInvoke du dispatcher de votre application.

Ne ratez plus aucune actualité avec la newsletter mensuelle de SoftFluent

Newsletter SoftFluent