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

Les deux révisions précédentesRévision précédente
Prochaine révision
Révision précédente
lang:csharp:thread [2018/11/21 17:24] – Ajout de "Invoke" rootlang:csharp:thread [2020/05/11 00:51] (Version actuelle) – Fix alignement des images root
Ligne 3: Ligne 3:
  
 =====Blocage inter-threads===== =====Blocage inter-threads=====
-====Sans valeur de retour====+[[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> <code csharp>
 public static void readControlText(Control varControl) { public static void readControlText(Control varControl) {
Ligne 15: Ligne 17:
 </code> </code>
  
-====Avec valeur de retour====+===Avec valeur de retour===
 <code csharp> <code csharp>
 public static string readControlText(Control varControl) { public static string readControlText(Control varControl) {
Ligne 29: Ligne 31:
 } }
 </code> </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.1542817459.txt.gz · Dernière modification : 2018/11/21 17:24 de root