Passer un traitement lourd en tâche de fond sans bloquer l'écran

Dans le temps les traitements lourds et chronophages devaient être gérés de telle sorte que les logiciels ne se bloquent pas complètement. Pour cela on devait disséminer la ligne

application.ProcessMessages;

un peu partout dans le traitement bloquant afin de l'empêcher de tout bloquer. Cela permettait à l'application de répondre aux sollicitations du système d'exploitation et de modifier l'affichage des composants que l'on aurait modifiés (par exemple une barre de progression, un compteur, une jauge, des graphiques, ...).

Les temps ont bien changé avec l'arrivée des processeurs multi threads puis multi coeurs.

Delphi et C++ Builder intègrent depuis pas mal de temps maintenant une classe TThread qui permet de sortir un traitement lourd du processus principal d'un programme.

Fort de son expérience dans ce domaine, Embarcadero a également mis en place des outils nous permettant de pousser les choses encore plus loin avec la parallélisation de traitements.

Sous Windows (depuis la version 95) qu'un programme se fige ne perturbait pas plus que ça le système d'exploitation. Cependant ceci n'est plus valable avec les applications mobiles qui doivent en permanence laisser l'utilisateur dans la capacité d'interagir avec elles et le système d'exploitation.

Une application figée c'est un message de l'OS proposant de la tuer et un utilisateur enclin à la mettre à la poubelle parce qu'il a l'impression qu'elle est plantée.

On doit désormais retenir et appliquer la règle suivante :

Les traitements synchrones bloquants sont à proscrire de tous les logiciels. Tout ce qui laisse supposer qu'un logiciel est bloqué doit être passé en asynchrone.

Sur le papier (ou à l'écran), cette règle est simple. A mettre en oeuvre elle ne l'est pas toujours car on tombe sur deux cas :

  • Soit le traitement bloquant calcule quelque chose que l'utilisateur a demandé. On ne doit donc rien lui proposer de faire d'autre en attendant la réponse mais sans bloquer le programme.
  • Soit le traitement bloquant fait quelque chose d'utile à l'application mais l'utilisateur n'a pas besoin d'avoir la réponse et peut continuer à faire d'autres choses.

Dans le premier cas, il faut donc ruser. Dans le second juste lancer une tâche de fond et la laisser tourner jusqu'à ce qu'elle soit complète.

Le gros avantage avec les classes et fonctions proposées par Embarcadero c'est qu'elles font partie de la RTL. Elles sont donc accessible à la fois aux applications console, aux applications "serveur", aux applications utilisant la VCL et aux applications utilisant Firemonkey. Elles sont également compilables dans les différentes cibles gérées par l'environnement, qu'il s'agisse de Windows, de Mac OS X, de iOS, d'Android ou même de Linux (depuis la version 10.2 Tokyo Entreprise de Delphi).

A la gestion de processus s'ajoutent des objets permettant de traiter l'accès partagé aux mêmes ressources et la transmission d'informations.

Les classes à consulter lorsque l'on fait de la programmation poussée dans ce domaine sont les suivantes :

  • TThread : pour la gestion des processus eux-mêmes et leur exécution.
  • TThreadList : pour gérer des groupes de processus et pouvoir connaître leur état les uns par rapport aux autres.
  • TEvent : pour partager simplement des informations d'état et des messages entre processus.
  • TMutex : pour protéger l'accès à des ressources partagées entre processus. Les habitués de Linux auront forcément déjà entendu parlé de cette notion d'exclusion mutuelle.
  • TCriticalSection : pour protéger l'accès à une section de code du programme, susceptible d'être utilisé par plusieurs processus mais devant ne s'exécuter qu'en un seul exemplaire à la fois par programme.
  • TSpinLock et TSpinWait : pour gérer des blocages de processus sans les interrompre complètement.
  • TMonitor : pour synchroniser des processus.

Du côté de la librairie de parallélisation :

  • TTask : équivalent de TThread mais pour l'exécution de processus parallèlisés.
  • TThreaPool : pour gérer la liste des processus parallélisés lancés par le programme en cours.
  • TParallel : pour simplifier le lancement de traitements parallèles à partir de boucles ou de jointures.
  • IFuture : cette interface permet de remplir une variable à partir du résultat d'un traitement basculé en tâche de fond.
    Inconvénient : son utilisation bloque le processus en cours le temps que le calcul soit terminé.
    Avantage : très franchement, même si je m'en suis déjà servi, je ne l'ai pas trouvé flagrant à cause de son inconvénient. Ceci dit, pour un calcul court, faisant appel à des informations extérieures comme l'API d'un site web, ça peut avoir son intérêt pour le développeur.

Il y a d'autres classes, functions et procédures utilisées pour jouer avec les processus, je vous laisse vous reporter à la documentation pour avoir le détail.

Je reviendrai rapidement sur l'utilisation des processus dans des exemples de cas pratiques. Vous pouvez aussi consulter la liste de mes articles sur les processus.


Mug carte postale SydneyMug carte postale Sydney