Utilisation des tâches et interactions avec le thread principal

Utilisation des tâches et interactions avec le thread principal - C#/.NET managed - Programmation

Marsh Posté le 01-12-2022 à 10:31:28    

Bonjour,
 
Je viens vers vous pour avoir des conseils sur l'utilisation des tâches en C#.
 
Dans une appli où je dois faire du traitement de masse, j'ai une datagrid avec en première colonne une icône qui indique quel élément est en cours de traitement.  
 
Le mode opératoire souhaité est le suivant :

  • Ma première ligne est traitée, mon icône prend la forme d'un gif animé de chargement,
  • J'appelle ma tâche pour traiter mon enregistrement sans figer l'écran,
  • Selon le résultat, la tâche doit mettre à jour la ligne (icône qui prend une forme différente selon le succès ou l'échec de la tâche),
  • Passage à la ligne suivante.


Je fais donc une boucle pour parcourir mon datagrid :
 

Code :
  1. for (int i = 0; i < datagrid.Rows.Count; i++) {
  2.    [...]
  3.    Task.Factory.StartNew(() =>
  4.    {
  5.       return matache(rowIndex, values);
  6.    });
  7. }


 
Et dans ma tâche, j'ai le code suivant :  
 

Code :
  1. public async Task matache(int rowIndex, Dictionary<string, string> values)
  2. {
  3.    [...]
  4.    datagrid.BeginInvoke(new Action(() =>
  5.    {
  6.       ((DataGridViewImageCell)datagrid[0, rowIndex]).Value = Resources.monimage;
  7.    }
  8. }


 
En opérant comme ça j'ai plusieurs problèmes :

  • Les tâches ne s'exécutent pas les unes après les autres (je comprends pourquoi même si ça m'embête),
  • mon "rowIndex" dans ma tâche matache() se retrouve parfois avec une valeur hors limite car, de ce que je comprends, même si ma tâche est lancée, mon rowIndex continue à évoluer dans la boucle for()


Que feriez-vous à ma place ? Quelles sont vos façons d'opérer ?
 
Si vous avez des liens d'exemple ou du code dont je pourrais m'inspirer afin de maitriser ce système de tâche, ça me serait d'une grande aide ! ;)
 
Merci pour vos retours

Reply

Marsh Posté le 01-12-2022 à 10:31:28   

Reply

Marsh Posté le 11-12-2022 à 10:28:19    

Tu veux que ça soit parallèle ou juste asynchrone (non bloquant pour l'UI) ?
Parce que la y a rien pour limiter le parallélisme, ça doit être un gros carnage sur une grille bien remplie.
 
Ton énoncé n'est pas très logique, tu parles de tâches qui doivent s'exécuter dans l'ordre mais à côté de ça tu balances un truc massivement parallèle.


---------------
Topic .Net - C# @ Prog
Reply

Marsh Posté le 11-12-2022 à 14:38:50    

C'est bien pour ça que je viens demander conseil figure toi  :heink:  
 
Si tu relis bien mon énoncé, je parle bien du fait que les tâches doivent s'exécuter dans l'ordre. Si je "balances un truc massivement parellele" c'est parce que c'est quelque chose de nouveau pour moi et que je ne maitrise pas. Mais on part tous de 0 un jour, alors je ne demande qu'à apprendre  :D  :D

Reply

Marsh Posté le 11-12-2022 à 17:15:51    

Tu peux partager un exemple complet ?
Et là j'essaierai de te l'adapter :p.
I'll y a des trucs pour limiter le nombre de threads déjà, ça sera moins chaotique.
Et j'ai beau relire, je trouve que tu ne réponds pas complètement à la question, justement parce que ce que tu fais dans le code n'est pas d'accord avec ce que tu écris, donc il faut bien demander à quoi se fier.
Je suis sur smartphone là (d'où la réponse partielle précédente) mais je pourrais t'en dire plus plus tard.


Message édité par TotalRecall le 11-12-2022 à 17:18:28

---------------
Topic .Net - C# @ Prog
Reply

Marsh Posté le 11-12-2022 à 18:02:08    

Citation :

Tu peux partager un exemple complet ?


 
Malheureusement non, ça m'aurait bien arrangé sinon  :(  
 
Mais je ne sais pas quoi dire de plus pour répondre d'avantage à ta question...  :D J'ai un lot de factures à envoyer. Mes factures sont représentées sous la forme d'une liste (datagrid). J'aimerai que les lignes soient traitées les unes après les autres, ce qui signifie que côté utilisateur il verra une animation sur la ligne 1 le temps que le nouveau thread lancé soit exécuté, puis une fois le traitement effectué l'animation passera à la ligne 2, puis à la ligne 3, etc.  
 
Ainsi, l'utilisateur verra au fur et à mesure le résultat du traitement (icône succès ou échec).
 
La méthode utilisée (thread ou pas), doit donc lancer un traitement, ne pas bloquer le thread principal (pour permettre les animations et empêcher le freeze de l'écran), puis retourner un résultat avant que le thread principal ne lance le traitement suivant.
 
Mais j'ai pu obtenir une réponse sur un autre forum, dans la boucle qui parcourt la datagrid, il suffit de lancer le traitement comme suit :  
 

Code :
  1. int nResult = await Task.Run(() =>
  2. {
  3.    return matache(rowIndex, values);
  4. });


 
Cet appel ne bloque pas le thread principal. De plus, le traitement attend la valeur de retour (nResult) pour la traiter (icône succès ou échec dans mon datagrid) et lancer ensuite le traitement suivant.
 
Du coup l'en-tête de ma fonction devient le suivant :
 

Code :
  1. public async Task<int> matache(int rowIndex, Dictionary<string, string> values) {}


 
Ça semble fonctionner comme je l'attend  :love:

Reply

Marsh Posté le 12-12-2022 à 17:47:26    

matheo265 a écrit :

...

 

Mais je ne sais pas quoi dire de plus pour répondre d'avantage à ta question...  :D J'ai un lot de factures à envoyer. Mes factures sont représentées sous la forme d'une liste (datagrid). J'aimerai que les lignes soient traitées les unes après les autres, ce qui signifie que côté utilisateur il verra une animation sur la ligne 1 le temps que le nouveau thread lancé soit exécuté, puis une fois le traitement effectué l'animation passera à la ligne 2, puis à la ligne 3, etc.

 

...


C'est pour ça que je te demandais si tu pouvais être bien clair entre asynchrone mais séquentiel, et parallèle. Ton explication texte semblait dire asynchrone, et ton code parallèle.
Comme tu disais ne pas être familier du sujet, je me permets de développer un peu (encore une fois c'est pour aider, ne le prend pas mal comme tu sembles avoir assez mal pris mon premier post) :
Sur une machine moderne, ça serait complètement sensé de vouloir lancer en parallèle autant de traitements que possible, il aurait juste
- fallu limiter la concurrence pour éviter des effets de bord et des ralentissements contre productifs (problème d'IO, problèmes de concurrence, context-switching...)
- Gérer l'ordre d'exécution pour que ça "suive" l'ordre de la grille.

 

Mais tu as répondu : ce que tu voulais c'est juste de l'asynchrone, non bloquant pour l'UI, pas du parallèle. Donc pas besoin de gérer tout ça (même si encore une fois selon la durée de tes traitements ça pourrait être intéressant pour accélérer l'ensemble, d'où le fait de demander combien de lignes on aura dans la grille et cie).  

 

Pour ton cas, effectivement la solution est plutôt simple : il suffit (en gros) d'ajouter des await, qui vont permettre que le thread exécutant attende le retour des tâches enfants, afin qu'elles soient jouées une par une.
Alors qu'avec l'exemple de ton premier post, tu lançais toutes les tâches quasiment en même temps, peu importe leur ordre et leur nombre.

 

Donc là avec simplement await, tu as un truc pas mal. A ta place je serais juste vigilant sur :
- La gestion des erreurs/exceptions dans les tâches enfants (à gérer localement ou à capturer à la fin de la tâche pour ne pas crasher l'ensemble). Parce qu'une exception dans une tâche n'est pas forcément gérée comme dans du code purement synchrone.
- Mettre à jour l'icône au lancement de la tâche, pas juste à la fin (je trouve que c'est plus propre).

 


Message édité par TotalRecall le 12-12-2022 à 17:48:13

---------------
Topic .Net - C# @ Prog
Reply

Marsh Posté le 12-12-2022 à 17:55:32    

Un background worker aurait surement suffit, c'est du windows forms je pari


---------------
Application 1km https://play.google.com/store/apps/ [...] ology.unkm
Reply

Marsh Posté le 12-12-2022 à 17:58:06    

Oui c'est clair, c'est aussi pour ça que je m'étonnais qu'il parte sur une solution relativement compliquée orientée multi thread alors qu'un BW aurait effectivement suffit.
 
Mais bon, avec les versions récentes de .Net faire du async / await, c'est bien aussi, et ça permet d'avoir un truc utilisable hors UI (alors que le background worker est justement orienté Winform). Donc vu que ça fonctionnera très bien, pas la peine de tout changer :D.


---------------
Topic .Net - C# @ Prog
Reply

Marsh Posté le 12-12-2022 à 18:20:01    

C'est une bibliothèque de classe mais qui utilise des winform effectivement. Comme l'a bien souligné TotalRecall, si la solution qu'on ma proposé (et que je comprends) fonctionne et qu'elle est propre, je vais rester dessus.
 
Les background worker, je connais pas, mais je vais me documenter car ça m'a l'air intéressant. Merci pour l'info  :jap:

Reply

Marsh Posté le 12-12-2022 à 18:51:47    

Les BW c'est le truc typique en .Net 1 / 2 pour faire des traitements asynchrones non bloquants dans du Winform avec le minimum de code, sachant qu'à l'époque sans ça il aurait fallu faire joujou avec les Threads à la main. C'est facile et pratique, mais old school.


Message édité par TotalRecall le 12-12-2022 à 19:12:32

---------------
Topic .Net - C# @ Prog
Reply

Marsh Posté le 12-12-2022 à 18:51:47   

Reply

Marsh Posté le 12-12-2022 à 19:51:43    

Oui, je suis en train de regarder et c'est exactement ce dont j'aurai eu besoin. Ce que je comprends pas c'est que durant mes multiples recherches je ne suis jamais tombé sur ça  :lol:  
 
Mais il y a une chose que vous pouviez pas deviner sans plus de code, et que j'aurai du vous préciser, c'est que dans ma fonction matache() j'utilise un objet HttpClient(), c'est pourquoi je m'étais dirigé vers une tâche asynchrone.  
 

Code :
  1. public async Task<int> matache(int rowIndex, Dictionary<string, string> values)
  2. {
  3.    HttpClient webClient = new HttpClient()
  4.    {
  5.       BaseAddress = new Uri("monurl" )
  6.    };
  7.    FileStream fsFile = File.OpenRead("pathtofile" );
  8.    StreamContent scFile = new StreamContent(fsFile);
  9.    HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Post, sUrl);
  10.    MultipartFormDataContent mfdcContent = new MultipartFormDataContent
  11.    {
  12.       { scFile, "file", "file.pdf" },
  13.       { new StringContent("value1" ), "param1" },
  14.       { new StringContent("value2" ), "param2" },
  15.       { new StringContent("value3" ), "param3" }
  16.    };
  17.    foreach (KeyValuePair<string, string> item in values)
  18.    {
  19.       mfdcContent.Add(new StringContent(item.Value), string.Format("par_{0}", item.Key));
  20.    }
  21.    requestMessage.Content = mfdcContent;
  22.    webClient.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json" ));
  23.    HttpResponseMessage webResponse = await webClient.SendAsync(requestMessage);
  24.    if (webResponse.IsSuccessStatusCode)
  25.    {
  26.       string retour = await webResponse.Content.ReadAsStringAsync().ConfigureAwait(true);
  27.       //traitement du retour et renvoi d'un int
  28.    }
  29. }

Reply

Marsh Posté le 12-12-2022 à 20:22:11    

Généralement .ConfigureAwait(true) ne sert à rien. Et la version avec false fait des trucs moches.
En gros quand c'est possible il vaut mieux ne pas appeler du tout ConfigureAwait.


---------------
Topic .Net - C# @ Prog
Reply

Marsh Posté le 12-12-2022 à 20:39:19    

ok, j'essaierai de supprimer ce morceau de code, surtout si c'est inutile ;)

Reply

Sujets relatifs:

Leave a Replay

Make sure you enter the(*)required information where indicate.HTML code is not allowed