Outils pour utilisateurs

Outils du site


lang:csharp:thread

Différences

Ci-dessous, les différences entre deux révisions de la page.

Lien vers cette vue comparative

Prochaine révision
Révision précédente
lang:csharp:thread [2018/01/17 17:16] – Ajout de l'instruction lock rootlang:csharp:thread [2020/05/11 00:51] (Version actuelle) – Fix alignement des images root
Ligne 1: Ligne 1:
 =====Instruction lock===== =====Instruction lock=====
 ''lock'' permet d'empêcher deux threads d'exécuter un code entouré de l'objet verrouillé. Attention, ''lock'' distingue uniquement des threads différents. Un ''lock(x)'' dans un ''lock(x)'' dans un même thread ne pose pas de problème, le deuxième ''lock'' n'étant pas bloquant. ''lock'' permet d'empêcher deux threads d'exécuter un code entouré de l'objet verrouillé. Attention, ''lock'' distingue uniquement des threads différents. Un ''lock(x)'' dans un ''lock(x)'' dans un même thread ne pose pas de problème, le deuxième ''lock'' n'étant pas bloquant.
 +
 +=====Blocage inter-threads=====
 +[[https://stackoverflow.com/questions/2214002/how-to-get-return-value-when-begininvoke-invoke-is-called-in-c-sharp|How to get return value when BeginInvoke/Invoke is called in C#]] {{ :lang:csharp:thread:net_-_how_to_get_return_value_when_begininvoke_invoke_is_called_in_c_-_stack_overflow_2020-02-07_10_22_03_am_.html |Archive du 06/02/2010 le 07/02/2020}}
 +====Sans aideur====
 +===Sans valeur de retour===
 +<code csharp>
 +public static void readControlText(Control varControl) {
 +  if (varControl.InvokeRequired) {
 +    varControl.Invoke((Action)delegate {readControlText(varControl);}));
 +  }
 +  else {
 +    // Faire ce qu'il faut.
 +  }
 +}
 +</code>
 +
 +===Avec valeur de retour===
 +<code csharp>
 +public static string readControlText(Control varControl) {
 +  if (varControl.InvokeRequired) {
 +    return (string)varControl.Invoke(
 +      new Func<String>(() => readControlText(varControl))
 +    );
 +  }
 +  else {
 +    // Renvoyer ce qu'il faut.
 +    return "";
 +  }
 +}
 +</code>
 +
 +====Avec aideur====
 +===Sans valeur de retour===
 +<code csharp>
 +/// <summary>
 +/// Aideur pour appeler une action soit directement, soit depuis un Invoke si nécessaire.
 +/// </summary>
 +/// <param name="control">Le controleur nécessitant l'Invoke.</param>
 +/// <param name="action">L'action a exécuter.</param>
 +public static void AutoInvokeAction(Control control, Action action)
 +{
 +    if (control.InvokeRequired)
 +    {
 +        control.Invoke((Action)delegate { action(); });
 +    }
 +    else
 +    {
 +        action();
 +    }
 +}
 +
 +AutoInvokeAction(wiz, () => { wiz.Focus(); });
 +</code>
 +<code csharp>
 +/// <summary>
 +/// Aideur pour appeler une fonction soit directement, soit depuis un Invoke si nécessaire.
 +/// </summary>
 +/// <typeparam name="T"></typeparam>
 +/// <param name="control">Le controleur nécessitant l'Invoke.</param>
 +/// <param name="function">La fonction a exécuter.</param>
 +/// <returns>Le retour de la fonction.</returns>
 +public static T AutoInvokeFunc<T>(Control control, Func<T> function)
 +{
 +    if (control.InvokeRequired)
 +    {
 +        return (T)control.Invoke(new Func<T>(() => function()));
 +    }
 +    else
 +    {
 +        return function();
 +    }
 +}
 +
 +return AutoInvokeFunc(control, () => { return true; });
 +</code>
 +
 +=====Création des threads=====
 +
 +====Thread classique====
 +<WRAP center round important 60%>
 +D'une manière générale, je déconseille l'utilisation de méthode lambda dès que la méthode doit accéder à des éléments extérieurs au contenu de la fonction lambda (''this'' par exemple). Cela pour bien avoir conscience des risques de race condition et de la traditionnelle erreur ''Cross-thread operation not valid: Control accessed from a thread other than the thread it was created on.''.
 +
 +{{:lang:csharp:thread:invalidoperationexception.png|}}
 +
 +Donc pour que chaque Thread reste bien indépendant, je conseille une classe statique contenant une seule méthode statique, qui sera celle appelée.
 +</WRAP>
 +
 +<code csharp>
 +public static class Simulation
 +{
 +  // Pas de risque de race condition, la valeur est modifiée
 +  // par une seule ligne de code extérieure et n'est pas modifiée par Execution().
 +  public static long finSimul;
 +
 +  public static void Execution()
 +  {
 +  }
 +}
 +
 +new Thread(new ThreadStart(Simulation.Execution)).Start();
 +</code>
 +
 +====Thread classique avec lambda expression====
 +[[https://gist.githubusercontent.com/senpost/930328/raw/0b72a2d2cc3e4456f4a9536d05208380b4842984/gistfile1.cs|gistfile1.cs]] {{ :lang:csharp:thread:gistfile1.cs |Archive du 23/11/2018}}
 +<code csharp>
 +Thread t = new Thread(() =>
 +{
 +  // Action à faire
 +});
 +t.Start();
 +</code>
 +
 +ou directement :
 +<code csharp>
 +new Thread(() =>
 +{
 +  // Action à faire
 +}).Start();
 +</code>
 +
 +====Thread se répétant====
 +<code csharp>
 +date = new System.Threading.Timer(
 +  (state) =>
 +  {
 +    // Faire ce qu'on veut.
 +  },
 +  this, 0, 1000); // Toutes les 1000 millisecondes.
 +</code>
 +
 +=====Threads accédant aux composants d'une form=====
 +Il faut arrêter les threads accédant aux composants d'une Form avant de la détruire. Sinon, des exceptions vont être générées.
 +
 +La solution la plus simple est d'autoriser l'arrêt brutale des threads avec ''t.IsBackground = true;''.
 +
 +Mais si on souhaite un arrêt non brutal des threads, on peut utiliser la classe ci-dessous.
 +
 +On crée un ''ManageThread'' par ''Form''. On y met dedans tous les threads à exécuter via ''Add''. La méthode ''Add'' démarre automatiquement le thread si l'ajout a été un succès.
 +
 +A la fermeture de la fenêtre, on appelle la méthode ''JoinAllThreads'' qui empêche l'ajout de nouveaux threads et attend que chaque thread en cours (qu'il gère) termine son exécution.
 +
 +<file csharp ManageThread.cs>
 +public class ManageThread
 +{
 +    /// <summary>
 +    /// Stockage de tous les threads.
 +    /// </summary>
 +    private List<Thread> allThreads = new List<Thread>();
 +
 +    /// <summary>
 +    /// Autorise ou pas les ajout de threads.
 +    /// Quand ManageThread est en train de purger tous les threads (via JoinAllThreads()),
 +    /// on empêche l'ajout de nouveaux.
 +    /// </summary>
 +    private bool allowAdd = true;
 +
 +    /// <summary>
 +    /// On ajoute un thread à la liste.
 +    /// En même temps, on s'assure sur tous les threads de la liste sont bien en vie.
 +    /// </summary>
 +    /// <param name="t">Le nouveau thread.</param>
 +    public bool Add(Thread t)
 +    {
 +        lock (allThreads)
 +        {
 +            for (int i = allThreads.Count - 1; i >= 0; i--)
 +            {
 +                if (!allThreads[i].IsAlive)
 +                {
 +                    allThreads.RemoveAt(i);
 +                }
 +            }
 +            if (!allowAdd)
 +                return false;
 +            allThreads.Add(t);
 +            return true;
 +        }
 +    }
 +
 +    /// <summary>
 +    /// On attend que tous les threads restants s'arrêtent tout seul.
 +    /// Si une fonction parallèle en ajoute aussi en boucle, on ne quitte jamais la fonction.
 +    /// </summary>
 +    public void JoinAllThreads()
 +    {
 +        lock (allThreads)
 +        {
 +            allowAdd = false;
 +        }
 +        while (true)
 +        {
 +            Thread t;
 +            lock (allThreads)
 +            {
 +                if (allThreads.Count == 0)
 +                    return;
 +                t = allThreads[0];
 +            }
 +            t.Join();
 +            lock (allThreads)
 +            {
 +                // Pas RemoveAt(0).
 +                allThreads.Remove(t);
 +            }
 +        }
 +    }
 +}
 +</file>
 +
 +Utilisation :
 +<code csharp>
 +public partial class Form1 : Form
 +{
 +    private ManageThread manageThread = new ManageThread();
 +    
 +    Thread t = new Thread(() =>
 +    {
 +        // Travail à faire.
 +    });
 +    // Si besoin, on peut vérifier si le thread a démarré.
 +    manageThread.Add(t);
 +    
 +    private void Form1_FormClosed(object sender, FormClosedEventArgs e)
 +    {
 +        // Ici, on arrête éventuellement tous les threads ajoutant des threads à manageThread.
 +        manageThread.JoinAllThreads();
 +    }
 +}
 +</code>
 +
 +=====Threads lambda utilisant une variable locale=====
 +[[https://blogs.msdn.microsoft.com/ericlippert/2009/11/12/closing-over-the-loop-variable-considered-harmful/|Closing over the loop variable considered harmful]] {{ :lang:csharp:thread:closing_over_the_loop_variable_considered_harmful_microsoft_docs_2020-02-07_10_22_10_am_.html |Archive du 12/11/2009 le 07/02/2020}}
 +
 +Mauvais :
 +<code csharp>
 +for (int i = 0; i < 2; i++)
 +{
 +    Thread t = new Thread(() =>
 +    {
 +        // i vaudra toujours 2 car le thread sera appelé après la boucle.
 +        Console.WriteLine(i);
 +    });
 +    t.Start();
 +}
 +</code>
 +
 +Bon :
 +<code csharp>
 +for (int i = 0; i < 2; i++)
 +{
 +    // Variable locale.
 +    int j = i;
 +    Thread t = new Thread(() =>
 +    {
 +        Console.WriteLine(j);
 +    });
 +    t.Start();
 +}
 +</code>
 +
 +<code csharp>
 +Parallel.For(0, 2, new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount },
 +    i =>
 +    {
 +        Console.WriteLine(i);
 +    }
 +);
 +</code>
 +Il est important de préciser le nombre maxi de threads sinon, si la charge baisse lors de l'accès à un fichier à cause d'un disque dur un peu lent, on peut se retrouver avec des dizaines de threads entraînant une saturation de la mémoire.
lang/csharp/thread.1516205786.txt.gz · Dernière modification : 2018/01/17 17:16 de root