Evitons des violations d'accès aléatoires dans les tâches

J'avais un projet qui tournait très bien sur de nombreuses versions de Windows jusqu'à la version 10.3 Rio de Delphi, mais voilà, il s'est mis à planter à partir de la version 10.4 en me générant des violations d'accès aléatoires et en n'exécutant ponctuellement plus les tâches que je lançais.

Sur mes ordinateurs de développement (des machines virtuelles Windows 10 sous VMWare fusion) pas d'anomalie.

Par change j'ai pu accéder à un poste de développement sur lequel le problème se produisait afin de l'identifier.

Voici en simplifiant beaucoup le bout de code que j'utilisais :

var
  MaTache1: ITask;

procedure MaProc1;
begin
  MaTache1 := TTask.Run(
    procedure
    begin
      while not(MaTache1.Status in [TTaskStatus.Canceled,
        TTaskStatus.Exception]) do
      begin
        sleep(5000);
      end;
    end);
end;

La variable MaTache1 permettait hors de cette procédure de connaître l'état de la tâche et de demander sa fermeture si besoin. Elle était accessible dans la procédure anonyme car celle-ci hérite du contexte de la zone de code dans laquelle elle est déclarée. En plus MaTache1 étant globale dans l'unité en cours, ça ne devait pas poser de problème pour y accéder ici.

Pas de bol...

Depuis les optimisations faites dans la librairie parallèle de Delphi 10.4 Sydney la tâche démarrait avant que la réponse de TTask.Run() ne soit affectée à MaTache1.

La variable n'étant pas assignée à une zone mémoire valide, violation d'accès au niveau du while qui teste sa propriété Status !

C'est tout con et ça n'aurait jamais dû fonctionner avec les versions antérieures, mais voilà un cas tout bête d'anomalie qui se révèle au fil du temps grâce à l'amélioration des performances dans la RTL. Un effet de bord un peu ennuyeux, mais la solution est toute bête.

var
  MaTache2: ITask;

procedure MaProc2;
begin
  MaTache2 := TTask.Run(
    procedure
    begin
      while not(TTask.CurrentTask.Status in [TTaskStatus.Canceled,
        TTaskStatus.Exception]) do
      begin
        sleep(5000);
      end;
    end);
end;

Plutôt que d'utiliser une variable extérieure (qui en plus devrait être protégée par un mutex pour limiter les erreurs d'accès multiples depuis plusieurs processus), on doit tout simplement utiliser les méthodes de la classe TTask pour accéder aux informations de la tâche en cours.
Bien entendu, dans un thread il faut utiliser les méthodes de la classe TThread pour accéder aux informations du thread en cours.

Morale de l'histoire : se méfier des variables globales ou locales embarquées automatiquement dans les procédures et méthodes anonymes car parfois la facilité peut coûter cher en production sur le long terme.


A lire aussi

Evitons des violations d'accès aléatoires dans les tâches (09/10/2020)
Les pièges de l'encodage lors de l'ouverture de fichiers textes. (21/09/2020)
Insérer un enregistrement dans un ensemble de données depuis une grille avec LiveBindings (30/05/2020)
Eviter les fuites de mémoire lors de la manipulation d'objets JSON (11/05/2020)
Pourquoi vouloir ajouter ()?: au Pascal alors qu'on a ifthen() ? (11/05/2020)
Plutôt INI ou JSON pour stocker vos paramètres ? (11/05/2020)
Quel composant utiliser pour dialoguer sur le port série de l'ordinateur ? (31/05/2019)
Utilisation de processus sous Delphi : fonctionnement de base. (30/07/2018)
Les threads et le blocage des écrans (30/07/2018)
Evitez les plantages causés par une mauvaise utilisation de la librairie System.JSON (18/04/2018)
Calculer et vérifier un checksum pour dialoguer avec l'extérieur (19/07/2017)
Télécharger simplement un fichier via Internet en tâche de fond (10/07/2017)
Calculer un MD5 sous Delphi (04/07/2017)
Passer un traitement lourd en tâche de fond sans bloquer l'écran (04/06/2017)
Ajouter des chaînes de caractères vides dans un objet JSON (19/05/2017)
Configurer le firewall de McAfee AntiVirus Plus pour utiliser l'App Tethering (28/06/2016)

Membre du programme MVP.
Membre du programme MVP