Architecture

MSDays de Rennes : des interfaces réactives avec WPF ? Oui, mais comment ?

Publié le : Auteur: William BAKOUMA Laisser un commentaire
architecture

Les utilisateurs Windows connaissent bien cette séquence où l’écran se fige avec un sablier qui n’en finit plus de tourner (XP) ou un cercle qui tourne indéfiniment (W7). Pendant cette séquence vous pouvez avoir un message dans le titre de la fenêtre indiquant « Windows ne répond plus ».

En général, Windows est rendu responsable de cette situation.
Et s’il n’en était rien ?

Très souvent, cette situation indique que le thread principal est surchargé et que le développeur a oublié de « laisser respirer » le thread UI en confiant la réalisation de certaines tâches à des threads secondaires.

Comment cela fonctionne t-il ?

Windows communique avec les applications et les fenêtres à l’aide de messages. Ces messages contiennent les actions à exécuter lorsque survient un clic de souris ou un appui sur une touche du clavier par exemple. Ces messages sont lus par une boucle de messages (message loop). Windows place ces messages dans une file d’attente (message queue) avant qu’une procédure ne prenne en charge le message (message pump).

La boucle de messages s’exécute dans le thread principal ou UI thread. Si ce thread se retrouve surchargé d’opérations consommatrices de ressources et ne pompe pas assez vite les messages, Windows le détecte et fige la fenêtre.

Pour éviter ce comportement, il faut dédier le thread principal à la gestion des interactions avec l’utilisateur et confier les traitements potentiellement gourmands à des threads secondaires pour utiliser tous les cœurs (CPU) disponibles.

Ce comportement observable en Windows Forms est aussi présent dans les applications WPF. A ceci près que WPF fournit des mécanismes prêts à l’emploi pour gérer ce type de problèmes (certains mécanismes étaient déjà présents dans Windows Forms).

Une application WPF démarre avec deux threads : UI thread et Rendering thread. Comme son nom l’indique, le Rendering thread se charge du rendu de l’interface (dessiner l’interface) et n’est pas accessible au développeur, alors que UI thread se charge de l’interaction avec l’utilisateur et est accessible au développeur.

Mais l’objectif est de développer le moins de traitements possible dans UI thread, et de paralléliser le plus possible ces traitements.

Les développeurs peuvent utiliser l’objet Dispatcher pour « faire respirer » l’application et émuler l’équivalent de Application.DoEvents() bien connu des développeurs Windows. Le Dispatcher permet de router les tâches pour une exécution dans UI thread. Il donne accès à la boucle des messages et peut être utilisé en Synchrone (Dispatcher.Invoke(…)) ou en asynchrone (Dispatcher.BeginInvoke(…)) Il faut cependant l’utiliser en tenant compte de l’affinité des threads et de la priorité des tâches.

Pour la gestion du multi-threading, le Framework .Net fournit plusieurs outils :

BackgroundWorker : Permet d’encapsuler un travail asynchrone. Le BackgroundWorker a été introduit dans le Windows Forms 2.0. Il utilise la classe SynchronisationContext et sait donc gérer des appels de méthodes avec synchronisation entre les threads. Dans la plupart des cas, le développeur utilisera BackGroundWorker plutôt que Dispatcher pour tous les traitements non UI.

DispatcherTimer : Permet l’exécution périodique d’un code. Un Timer est exécuté sur le thread UI. Le DispatcherTimer utilise le « répartiteur » (Dispatcher) et la priorité.

ThreadPool : Certainement la manière la plus simple de faire du multi-threading avec WPF. Il s’agit d’un pool de threads prêts à l’emploi auquel le développeur peut confier l’exécution de tâches. Ce sont des threads « managés » d’arrière plan.

TPL (Task Parallel Library) : Cette library fournit les classes permettant de faire de la programmation parallèle. Elle simplifie la gestion de la concurrence et de la synchronisation lors de l’exécution de traitements parallèles et introduit la notion de « Tâche ».

Mais attention ! L’utilisation de threads doit être sous contrôle, « Trop de threads tue le thread ». Le Framework .Net fournit des outils permettant de contrôler nos threads comme les semaphores, mutex etc. En particulier, il faut tenir compte du fait que les ressources sont limitées : nombre de CPUs, nombre de connexion réseau parallèles, concurrence d’accès au matériel etc.

Il n’y aurait donc pas assez de threads dans nos applications. Et pour cause !

La programmation des threads est restée assez longtemps « obscure » dans les langages Microsoft et peu de développeurs s’y sont risqués, préférant éviter les problématiques de synchronisation et d’accès aux ressources. Cette raison ne tient plus dans la mesure où le Framework a évolué et fournit désormais des outils de haut niveau permettant de programmer des threads sans y toucher !