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.


Mug Toucan DX dans la baie de RioMug carte postale Sydney