Affichage des articles dont le libellé est C#-Threading. Afficher tous les articles
Affichage des articles dont le libellé est C#-Threading. Afficher tous les articles

lundi 18 octobre 2010

Threading en C# - exemple event-based asynchronous pattern (AVEC réentrance)

Comme précisé dans l'article "Threading en C# - synchronisation et méthodes de threading", voici un exemple d'implémentation event-based asynchronous pattern (utilisant le ThreadPool) et supportant la réantance.
L'article précédent "Threading en C# - exemple event-based asynchronous pattern (sans réentrance)" implémentait le même pattern mais sans réentrance

Note: les exemples sont développés avec Snippet Compiler.

L'implémentation du pattern  "Event-based asynchronous pattern" n'est pas des plus simple et doit être scrupuleusement suivit.
Cet exemple est issue d'un article MSDN supportant la réentrance et implémentant le pattern pour un composant (ce qui le cas d'utilisation le plus approprié de ce pattern).
Tout comme l'exemple précédent, l'implémentation est faite autour d'une simple classe (au lieu d'un composant).

Code de test
public class MyClass
{
    public static void RunSnippet()
    {
        List<string> sourceList = new List<string>();
        sourceList.Add( "Test 1" );
        sourceList.Add( "This Stuff text" );
        sourceList.Add( "Some other tests" );
        sourceList.Add( "dodo" );


        // Test of the Re-entrant EventBased Asynchronous pattern
        // The unique taskId identifier will be the string reference himself
        TestClass myTest = new TestClass();
        myTest.ComputeTextCompleted += ComputeTextCompletedCallback;
        foreach( String str in sourceList ) {                        
            myTest.ComputeTextAsync( str, str );  // appels multiples :-)
WL( String.Format( "ComputeTextAsync called for \"{0}\"", str ));
        }

        // Cancel while working
        WL( String.Format( "Send cancellation for {0}", sourceList[2] ) );
        myTest.CancelTextAsync( sourceList[2] );
    }
    
    public static void ComputeTextCompletedCallback( object sender, ComputeTextCompletedEventArgs e ) {
        // The unique identifier e.TaskID is the reference to the source text
        if( e.Canceled ) {
            WL( "" );
            WL( String.Format( "ComputeTextCompletedCallback() received cancellation for \"{0}\" :-( ", (string)(e.TaskId) ));
            WL( "" );            
        }
        else {
            WL( "" );
            WL( String.Format( "ComputeTextCompletedCallback() received computed text for \"{0}\"", (string)(e.TaskId) ));
            WL( e.ComputedText );
            WL( "" );
        }
    }
    ...
}

Résultat du code de test
ComputeTextAsync called for "Test 1"
ComputeTextAsync called for "This Stuff text"
ComputeTextAsync called for "Some other tests"
ComputeTextAsync called for "dodo"
Send cancellation for Some other tests
Press any key to continue...  notez que le code appelant s'interrompt ici
ComputeTextCompletedCallback() received cancellation for "Some other tests" :-(

ComputeTextCompletedCallback() received computed text for "This Stuff text"
 *$+-*/This Stuff text*$+-*/

ComputeTextCompletedCallback() received computed text for "Test 1"
 *$+-*/Test 1*$+-*/

ComputeTextCompletedCallback() received computed text for "dodo"
 *$+-*/dodo*$+-*/

Code Source
Fichier: Threading_EventBasedAsynchronousPattern_Reentrancy.cs

Si le code de test démontre le bon fonctionnement de l'ensemble, c'est surtout le code implémentant le pattern qui est le plus intéressant.
Le code n'est pas publié directement dans ce post car il est assez long. Il est par contre accessible via le lien vers le fichier Threading_EventBasedAsynchronousPattern_Reentrancy.cs.

A toute fin utile, le précédent exemple "Threading en C# - exemple event-based asynchronous pattern (sans réentrance)" était accompagné d'un Diagramme de séquence détaillant le fonctionnement du pattern. Le fonctionnement reste identique dans les deux cas (avec et sans réentrance).

vendredi 8 octobre 2010

Threading en C# - exemple event-based asynchronous pattern (sans réentrance)

Comme précisé dans l'article "Theading en C# - synchronisation et méthodes de threading", voici un exemple d'implémentation event-based asynchronous pattern (utilisant le ThreadPool) sans support de la réentrance.
Note: les exemples sont développés avec Snippet Compiler.

L'implementation du pattern  "Event-based asynchronous pattern" n'est pas des plus simple et doit être scrupuleusement suivit.
Cet exemple est issue d'un article MSDN supportant la réentrance et implémentant le pattern pour un composant (ce qui le cas d'utilisation le plus approprié de ce pattern).
Pour le besoin de l'exemple et du test, j'ai abandonné  l'implémentation de type Composant pour favoriser l'implémentation du pattern dans la classe de test  TestClass.

Code de test
public class MyClass
{
 public static void RunSnippet()
 {
  // Test of the NON recurrent EvebntBased Asynchronous pattern
  //    (cannot be called several times while processing async event)
  TestClass myTest = new TestClass();
  myTest.ComputeTextCompleted += ComputeTextCompletedCallback;
  myTest.ComputeTextAsync( "This Stuff text" );
  
  try {
   myTest.ComputeTextAsync( "This test will fire exception" );   
  }
  catch(Exception e ){
   WL( "Cannot recall non recurrent Async while working!" );
   WL( String.Format("Exception while calling ComputeTextAsync(). {0} with message \"{1}\"", e.GetType().Name, e.Message ));
  }
  
  // Wait while async call is busy
  while( myTest.IsBusy )
   Thread.Sleep( 10 );
  myTest.ComputeTextAsync( "Second test" );

  // Cancel while working
  while( myTest.IsBusy )
   Thread.Sleep( 10 );
  myTest.ComputeTextAsync( "Third test" );
  Thread.Sleep( 500 );
  myTest.CancelAsync();
  
  // Wait to exit
  while( myTest.IsBusy )
   Thread.Sleep( 10 );  
 }
        ...
}

Résultat du code de test
Cannot recall non recurrent Async while working!
Exception while calling ComputeTextAsync(). ArgumentException with message "Task
 ID parameter must be unique
Nom du paramètre : taskId"

ComputeTextCompletedCallback() received computed text :-)
---This Stuff text---


ComputeTextCompletedCallback() received computed text :-)
---Second test---

Press any key to continue...
ComputeTextCompletedCallback() received cancellation :-(

Code Source
Fichier: Threading_EventBasedAsynchronousPattern.cs

Si le code de test démontre le bon fonctionnement de l'ensemble, c'est surtout le code implémentant le pattern qui est le plus intéressant.
Mais ce code n'est pas publié directement dans ce pot car il est assez long. Il est par contre accessible via le lien vers le fichier Threading_EventBasedAsynchronousPattern.cs.

Dans ce code, le traitement est pris en charge par la worker méthode ComputeTextWorker.
Afin de faciliter la compréhension de l'ensemble, le graphique suivant reprend un descriptif des différents appels (avec quelques annotations).

Classe MyClass:
Classe de test (l'appelant) du snippet permettant de tester l'exactitude de l'implémentation du pattern.
Cette classe est utilisée pour faire l'appel à ComputeTextAsync et attend le résultat fournit par l'évènement ComputeTextCompleted.

Classe TestClass:
Implémente le pattern "Event Based Asynchronous Pattern".
C'est le fonctionnement de cette classe qui est testée.

Cliquer pour agrandir
1*: Utilisation du OnCompletedDelegate via une opération asynchrone pour que l'exécution se fasse sur le thread approprié.
2*: La méthode OnComputeTextCompleted sert a déclencher l'évènement ComputeTextCompleted.
3*: Appel la méthode ComputeTextCompleteCallback de MyClass.

jeudi 30 septembre 2010

Threading en C# - exemple Asynchronous Delegate

Comme précisé dans l'article "Theading en C# - synchronisation et méthodes de threading", voici un exemple d'utilisation des asynchronous delegates (sur le ThreadPool).
Note: les exemples sont développés avec Snippet Compiler.

L'implementation d "Asynchronous Delegate" a le mérite de facilité le passage de paramètres, d'autoriser le retour de plusieurs valeurs (via ref/out) et de capturer les exceptions.

Exemple 1:
Assez rudimentaire il présente l'utilisation d'un asynchronous delegate
Fichier:  Threading_AsynchronousDelegate.cs
using System;
using System.Collections.Generic;
using System.Threading;

public class MyClass
{
 public delegate void DoWork ( out string workerMessage );
 
 public static void RunSnippet()
 {
  // Take the Delegate
  //    Instanciate the Delegate with to a correct method signature
  DoWork FillTheBottle = DoWork_FillTheBottle;
  // NB:
  //    If DoWork_FillTheBottle was not Static we would use...
  //    DoWork FillTheBottle = (DoWork)(new MyClass().DoWork_FillTheBottle);
  
  
  // Fire Async call
  //    parameters are deletage signature parameters + Callback + Data objet
  String sMessage = String.Empty;
  IAsyncResult cookieFillTheBottle = FillTheBottle.BeginInvoke( out sMessage, null, null );
  
    
  // Join the Async call
  FillTheBottle.EndInvoke( out sMessage, cookieFillTheBottle );
  WL( "Fill the Bottle returned: "+sMessage );
 
 }
 
 public static void DoWork_FillTheBottle(out string sMessage){
  for( int i=0; i<3; i++ ){
   WL( "  GlouGlou... filling the bottle" );
   Thread.Sleep( TimeSpan.FromSeconds(1) );
  }
  sMessage= "Voila, I filled the bottle";
 }
 
 ...
}

Exemple 2:
Cet exemple un peu plus détaillé montre l'utilisation de plusieurs workers, avec des signatures différentes et la capture des exceptions.
Fichier: Threading_AsynchronousDelegate2.cs

mercredi 29 septembre 2010

Threading en C# - exemple ThreadPool.RegisterWaitForSingleObject

Comme précisé dans l'article "Theading en C# - synchronisation et méthodes de threading", voici un exemple d'utilisation du RegisterWaitForSingleObject (sur le ThreadPool).
Note: les exemples sont développés avec Snippet Compiler.


Comme précisé dans l'article, l'utilisation de RegisterWaitForSingleObject convient particulièrement aux tâches qui s'attendent les unes les autres.
Rien de tel qu'un State Diagram pour illustrer cet exemple... et pourquoi pas en préparant du café.


Petite subtilité par rapport au diagramme:
Le état "Tenir le café au chaud" perdurera tant que l'état (la routine de traitement) ne reçoit pas le signal que quelqu'un prend du café (cfr: le ManualResetEvent "I_Take_Coffee").
En quittant son état "Tenir le café au chaud", la routine de traitement lancera l'ordre d'extinction automatique de la machine a café (cfr: le AutoResetEvent "AutoTurnOffCoffeeMachine")


Transformation du diagramme en code:
Chacun des états est implémenté dans un Worker (routine de traitement). A savoir:
  • DoWork_FillingCoffeeMachine
  • DoWork_CookCoffee
  • DoWork_KeepCoffeeWarm
  • DoWork_TurnOffCoffeeMachine
Chacune des [transitions] est prit en charge par un WaitHandle (soit un ManuelReset event, AutoResetEvent) servant de "Signal" d'évènement. A savoir:
  • Start - Permet de démarrer la State Machine
  • Filled - Permet de savoir que le réservoir est remplis
  • Cooked - Permet de savoir que la café est passé/fait.
  • AutoTurnOffCoffeeMachine - Permet de savoir qu'il faut éteindre la machine.
  • Exit - Permet au thread principal de savoir que l'on est sorti de la State Machine.
  • I_Take_Coffee - Permet au worker DoWork_KeepCoffeeWarm d'être informé que l'utilisateur a prit du café.
A noter que l'évènement "Cooked" est utilisé à la fois dans le thread principal (pour autoriser à prendre du café) et dans le ThreadPool (pour commencer à garder le café au chaud).
Le signal "Cooked" ne peut donc pas être implémenté à l'aide d'un AutoResetEvent (sinon, un seul des deux threads pourrait acquerir le signal) mais à l'aide un ManualResetEvent.

Résultat de l'exécution:
Filling the Coffee machine
   Glou..Glou...
   Glou..Glou...
   Glou..Glou...
   Glou..Glou...
   Glou..Glou...
   Filled! (Send Filled signal)
Cooking the Coffee
   Heating...Heating...
   Heating...Heating...
   Heating...Heating...
   Cooked! (Send Cooked signal)
The user is allowed to take some coffee
Press ENTER to take some coffee
Keeping the coffee warm
   Warm...Warm... Keeping the coffee warm
   Warm...Warm... Keeping the coffee warm
   Warm...Warm... Keeping the coffee warm
   Warm...Warm... Keeping the coffee warm
   Warm...Warm... Keeping the coffee warm

   Yes! User did take coffee. (Send TurnOff signal)
Turning off the Coffee machine
Turned off
Exit signal received on main thread.

Le code source:
Fichier: Threading_ThreadPool_RegisterWaitForSingleObject.cs

using System;
using System.Collections.Generic;
using System.Threading;

public class MyClass
{
 // To signal for Start the whole Coffee process
 static AutoResetEvent Start = new AutoResetEvent(false);
 // To Signal that the coffee machine is full of water
 static AutoResetEvent Filled = new AutoResetEvent(false);
 // To signal that coffee is cooked
 //     Main Thread and DoWork_KeepCoffeeWarn is listening this event.
 //     AutoResetEvent cannot be user otherwise only one thread will acquire the signal.
 static ManualResetEvent Cooked = new ManualResetEvent(false); 
 // To signal that the Coffee machine can be auto turned off
 //     (because the user did take some coffee) 
 static AutoResetEvent AutoTurnOffCoffeeMachine = new AutoResetEvent(false);
 // The process did complete, Main thread can exit.
 static AutoResetEvent Exit = new AutoResetEvent(false); 
 
 // Main thread signaling DoWork_KeepCoffeeWarn that user takes coffee
 //   (pressed Enter). The DoWork_KeepCoffeeWarn can stop to wan
 static ManualResetEvent I_Take_Coffee = new ManualResetEvent(false); 
 
 public static void RunSnippet()
 {
  //=== Register ThreadPoll workers ===
  ThreadPool.RegisterWaitForSingleObject( Start, DoWork_FillingCoffeeMachine, null, -1, false );
  ThreadPool.RegisterWaitForSingleObject( Filled, DoWork_CookCoffee, null, -1, false );
  //NB: Execute DoWork_KeepCoffeeWarm only ONCE because linked to ManualResetEvent, 
  //    Otherwise, it would be executed an infinitely of times.
  ThreadPool.RegisterWaitForSingleObject( Cooked, DoWork_KeepCoffeeWarm, null, -1, true ); 
  ThreadPool.RegisterWaitForSingleObject( AutoTurnOffCoffeeMachine, DoWork_TurnOffCoffeeMachine, null, -1, false );  
  
  //=== Start ====
  Start.Set();
  
  // Wait that the coffee is cooked before allowing the user to take coffee
  Cooked.WaitOne();
  
  // Wait Carriage Return and Signal that I did take coffee
  WL("The user is allowed to take some coffee");
  WL( "Press ENTER to take some coffee" );
  RL();
  I_Take_Coffee.Set();
  
  // Wait that DoWork_TurnOffCoffeeMachine signal the Exit flag.
  Exit.WaitOne(); 
  WL( "Exit signal received on main thread." );
 }
 
 public static void DoWork_FillingCoffeeMachine( object data, Boolean timedOut){
  WL("Filling the Coffee machine" );
  for( int i = 0; i < 5; i++ ){
   WL( "   Glou..Glou..." );
   Thread.Sleep( TimeSpan.FromSeconds( 1 ) );   
  }
   
  WL("   Filled! (Send Filled signal)" );
  Filled.Set();
 }
 
 public static void DoWork_CookCoffee( object data, Boolean timedOut ){
  WL("Cooking the Coffee" );
  for( int i = 0; i < 3; i++ ){
   WL( "   Heating...Heating..." );
   Thread.Sleep( TimeSpan.FromSeconds( 1.5 ) );   
  }
  WL("   Cooked! (Send Cooked signal)" );
  Cooked.Set();
 }

 public static void DoWork_KeepCoffeeWarm( object data, Boolean timedOut ){
  WL("Keeping the coffee warm" );
  while( true ){
   WL( "   Warm...Warm... Keeping the coffee warm" );
   // Simulate 0.5 second of processing
   Thread.Sleep( TimeSpan.FromMilliseconds( 500 ) );   
   // Wait 2 seconds for signal
   // Break the while loop thread is signaled by user taking the coffee
   if( I_Take_Coffee.WaitOne( TimeSpan.FromSeconds(2) ) )
    break;
  }
  WL("   Yes! User did take coffee. (Send TurnOff signal)" );
  AutoTurnOffCoffeeMachine.Set();
 }
 
 public static void DoWork_TurnOffCoffeeMachine( object data, Boolean timedOut ){
  WL("Turning off the Coffee machine" );
  Thread.Sleep( TimeSpan.FromSeconds( 1 ) );
  WL("Turned off" );
  Exit.Set();
 }
        ....
}

mardi 28 septembre 2010

Threading en C# - exemple ThreadPool.QueueUserWorkItem

Comme précisé dans l'article "Theading en C# - synchronisation et méthodes de threading", voici un exemple d'utilisation du QueueUserWorkItem (sur le ThreadPool).
Note: les exemples sont développés avec Snippet Compiler.

Ce premier exemple empile 100 workers sur le ThreadPool et attend la fin de l'exécution de tous les worker thread.
Join Pattern pour QueueUserWorkItem
Pour attendre la fin de l'exécution des WorkItems, le programme implémente le Join Pattern à l'aide d'une structure Wait & Pulse et d'un compteur (de 100 à 0, décrémenté comme avec les sémaphores).
Attention que dans un système Wait & Pulse, le Wait doit absolument arrivé avant le premier Pulse (ce qui sera le cas de cet exemple).
Fichier: Threading_ThreadPool_QueueUserWorkItem.
using System;
using System.Collections.Generic;
using System.Threading;

public class MyClass
{
 static object workerLocker = new object();
 static int runningWorkers = 100;
  
 public static void RunSnippet()
 {
  for( int i = 0; i < runningWorkers; i++ )
   ThreadPool.QueueUserWorkItem( DoWork, i );
  WL( "Wait for threads to complete" );
  lock( workerLocker ){
   while( runningWorkers > 0 )
    Monitor.Wait( workerLocker );
  }
 }
 
 public static void DoWork( object instanceData ){
  WL( "Started: "+instanceData );
  
  // Exception Handling must be managed in the Worker method.
  //if( (int)instanceData == 10 )
  // throw new Exception("SBlaff");
  
  Thread.Sleep( 1000 );
  WL( "Ended: "+instanceData );
  lock(workerLocker) {
   runningWorkers--;
   Monitor.Pulse( workerLocker );
  }
 }
 
}

Threading en C# - exemple BackgroundWorker

Comme précisé dans l'article "Theading en C# - synchronisation et méthodes de threading", voici deux exemples d'utilisation des background worker.
Note: les exemples sont développés avec Snippet Compiler.

Ce premier exemple est le plus simple... consiste juste en la création d'un Background Worker.
Fichier: Threading_BackgoundWorker.cs.

public class MyClass
{
 static BackgroundWorker bw;
  
 public static void RunSnippet()
 {
  bw = new BackgroundWorker();
  bw.WorkerReportsProgress = true;
  
  bw.DoWork += bw_DoWork;
  bw.RunWorkerAsync( "Message to display"  );
 }
 
 static void bw_DoWork( object sender, DoWorkEventArgs e ){
  Console.WriteLine( e.Argument );
 }   
 ...
}

Ce deuxième exemple met en place le processus de reporting, cancellation et événement complete.
Ce deuxième exemple documente également une méthode (proposé sur MSDN) pour simuler le Join Pattern pour un BackgroundWorker.
En effet, un BackgroundWorker travaille en tâche de fond et le thread principal n'est pas censé attendre la fin de son exécution. Une autre solution serait de partager un AutoResetEvent.
Fichier: Threading_BackgoundWorker_Progress.cs.

using System;
using System.Collections.Generic;
using System.Threading;
using System.ComponentModel;

public class MyClass
{
 static BackgroundWorker bw;
 public static void RunSnippet()
 {
  bw = new BackgroundWorker();
  bw.WorkerReportsProgress = true;
  bw.WorkerSupportsCancellation = true;
  bw.DoWork += bw_DoWork;
  bw.ProgressChanged += bw_ProgressChanged;
  bw.RunWorkerCompleted += bw_RunWorkerCompleted;
  
  bw.RunWorkerAsync ("Running the worker" );
    
  /* === Wait the BackgroundWorker ====
  The Join pattern does not exits for background worker.
  background are supposed to offer background processing while keeping the UI responsive.
  
  Anyway, if you need to wait for a worker to finish (eg: downloaded), here a solution proposed on MSDN. 
     http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.isbusy.aspx
   
  while (bw.IsBusy)
     {
         // Perform some UI progression
   //     progressBar1.Increment(1);
   
         // Keep WinForm UI messages moving, so the form remains 
         // responsive during the asynchronous operation.
         Application.DoEvents();
     } 
  */
 
  WL( "5 Seconds to press ENTER... this will send a Cancel to the worker");
  RL();
  if( bw.IsBusy )
   bw.CancelAsync();
  WL( "End of program" );
  
 }
 
 public static void bw_DoWork( object sender, DoWorkEventArgs e ){
  for( int i = 0; i <= 100; i+=20){
   if( bw.CancellationPending ){
    e.Cancel = true;
    return;
   }
   bw.ReportProgress(i);
   Thread.Sleep( 1000 );
  }
  e.Result = 123; // value for RunWorkerCompleted event
 }
 
 public static void bw_RunWorkerCompleted( object sender, RunWorkerCompletedEventArgs e ) {
  if( e.Cancelled )
   WL( "Worker has been cancelled!");
  else 
   if (e.Error != null)
    WL( String.Format("Worker got an exception {0} with message {1}", e.GetType().ToString(), e.Error.ToString()) );
   else
    WL( String.Format("Worker completed with value {0}", e.Result ));
 }
 
 public static void bw_ProgressChanged( object sender, ProgressChangedEventArgs e ){
  WL( String.Format( "Reached {0}% value", e.ProgressPercentage) );
 }
        ...
 
}

lundi 27 septembre 2010

Theading en C# - synchronisation et méthodes de threading

Joseph Albahari (auteur de C# in a nutshell) met à disposition une documentation très complète sur le Threading en C#.
Que cela soit sur son site internet ou le document pdf, la lecture des 80 pages vaut largement le détour.

Il faudra cependant s'accrocher un peu car le sujet et retourné dans tous les sens.
Joseph Albahari aborde également de façon approfondie un élément essentiel du threading, à savoir les méthodes de synchronisation.

Contenu de cet article
Il est impossible de résumer le threading C# dans un seul article.
J'ai décidé de seulement énumérer les méthodes de synchronisation. C'est un sujet sur lequel il existe une grande littérature et l'article de Joseph Albahari suffit amplement pour ce sujet.
Je vais par contre fournir une vue globale des différentes méthodes permettant d'implémenter le multithreading en C# (il y en a 8).
Si mon résumé est assez sommaire, Joseph Albahari fournit bien évidemment une information détaillée sur chacun des points.

Finalement, cet article est encore loin d'être complet, je voudrais y ajouter des exemples, parler du Local Storage et bien entendu, il me reste encore toute la section des synchronisations non bloquantes (Wait/pulse, Memory Barriers, Volatiles, etc).
Les exemples seront certainement produits dans d'autres articles qui seront repris en référence depuis cet article.
Le reste du sujet sera certainement traité dans une autre publication.


La synchronisation
C'est aussi que l'on aborde les méthodes de synchronisations:
  • Locking, les objets de synchronisation, Join
  • AutoResetEvent et ManualResetEvent
  • Mutex, Sémaphore
    EventWaitHandle, WaitOne, WaitAll, SignalAndWait.
  • Implémentation d'un Acknowledgement
    Utilisation de deux AutoResetEvent pour implementer une processus ready/go
  • Implémentation d'un Producer-Consumer queue.
    Attention, le framework .Net propose également une implémentation d'un ProducerConsumerQueue.
    L'article permet cependant de se faire une idée de l'utilité d'une telle structure.
  • Wait and Pulse
    largement décrit
  • ReaderWriterLockSlim 
L'article "Sync C# - Méthodes de synchronisation" à été spécifiquement créé pour traiter de ce sujet particulier.

Le threading
Sur le plan des threads, on aborde inévitablement la création des threads.
Il faut savoir qu'il existe bien des façons de mettre en place le threading dans le Framework .Net.
En voici un relevé exhaustif basé sur l'excellent document de Joseph Albahari (déjà évoqué dans l'introduction).

1) Thread
Principe d'implémentation basé sur une méthode appelée via un delegate.

L'article de Joseph Albahari (voir ci-dessus) s'étend longuement sur l'utilisation de la classe Thread.
Il est également possible de créer sa propre classe dérivée de Thread.
  • Permet l'utilisation de Join()
  • Nécessite l'implémentation d'un Try/Catch

2) BackgroundWorker
BackGroundWorker est une classe helper disponible dans le namespace System.ComponentModel.

Voici les caractéristiques principales:
  • Permet l'interaction avec WinForms.
  • Utilise des delegates pour attacher la méthode de tavaille (DoWork)
  • Permet de démarrer le worker avec un paramètre.
    RunWorkerAsync("Hello World");
  • Capture les exception (signalé lors de l'achèvement du Worker).
  • Utilise des évènements pour signaler la progession (avec possibilité de cancel)
    Voir
    WorkerReportsProgress, ProgressChanged, WorkerSupportsCancellation.
  • Signaler l'achèvement avec état cancelled  ou Error
    Voir RunWorkerCompleted.
  • Autorise la surcharge.
Avantages:
  • Facile à coder (utilisation de delagate).
  • Interaction avec WinForms (progression).
  • Gestion des exceptions par le BackgroundWorker.
Ressources:

3) ThreadPool.RegisterWaitForSingleObject
Le ThreadPooling de ce type est idéal pour les tâches threadés qui s'attendent les unes les autres (synchronisation avec ManualResetEvent).
Cela évite la création de nombreuses instances de thread qui passent un temps non négligeable en état bloqué.

Ici, c'est le gestionnaire du thread pool qui attend le signal d'exécution (via le ManualResetEvent) et alloue le thread d'exécution à "la demande".
Par exemple, pour 20 tâches planifiées qui s'attendent les unes les autres, seul 5 threads physique concurrents pourrait être vraiment nécessaires pour l'exécution. C'est ce que l'on obtiendra en utilisant le ThreadPool.

  • Enregistre un DoWork delegate et un ManuelResetEvent (WaitHandle) sur le ThreadPool.
  • La tâche démarre lorsqu'on signale le WaitHandle.
  • RegisterWaitForSingleObject accepte un paramètrage (passé à la méthode DoWork)
  • Ne jamais utiliser Abord (qui empêche le recyclage du thread sur le ThreadPool)
  • Nécessite l'implémentation d'un Try/Catch dans la méthode DoWork
Avantages: 
  • Minimiser l'utilisation des ressources processeur (diminue le nbre de Thread physique nécessaire à l'exécution)... donc diminue le nbre de context switching sur le scheduler du système d'exploitation.
  • Threadpool limite le nbre de thread a 25 (par défaut). 
Ressource:

4) ThreadPool.QueueUserWorkItem
Permet d'enfiler (enqueue) des tâches exécutées immédiatement sur le ThreadPool.
Pour rappel, le ThreadPool gère un pool de 25 threads par défaut.

Ainsi, si l'on enfile 50 tâches d'un coup, seules les 25 premières seront schedulée, les autres tâches resterons dans la file d'attente (en attente de libération d'un thread).
  • Tâche enfilée démarre immédiatement.
  • Recommandations identiques à ThreadPool.RegisterWaitForSingleObject.
Ressources:
5) Asynchronous Delegate
Probablement l'une des méthodes les plus faciles et les plus intéressantes de mettre the multithreading en oeuvre. Par ailleurs, elle exploite toujours le ThreadPool.
Cette méthode se base sur un delegate et l'interface IAsyncResult (permettant le rendez-vous).
  • EndInvoke est utilisé au point de rendez-vous.
  • Les exceptions sont attrapées et relancées aux point de rendez-vous (IAsyncResult.EndInvoke).
  • EndInvoke supporte de façon transparente plusieurs ref/out paramètres en provenance de la méthode déléguée.
  • L'objet IAsyncResult retourné par BeginInvoke dispose d'une propriété IsCompleted.
  • BeginInvoke peut recevoir plusieurs paramètres (comme défini par la signature du delegate)
  • BeginInvoke peut recevoir des paramètres optionnels une fonction callback et un DataObject (tout deux optionnels).
Ressources:
6) Asynchronous Methods
Pattern particulier permettant de déclarer sur un objet une méthode dont l'exécution sera asynchrone.
Ce type de méthode commence par Begin (pour les méthodes de démarrage) et End (pour les méthodes rendez-vous).
Les méthodes asynchrones permettent d'implémenter un traitement ou l'activité dépasse largement le nombre de thread disponible. Mais bien entendu, dans ce cas, l'exécution ne se fait pas vraiment en parallèle (cela ne pourrait d'ailleurs pas être garanti).
A titre d'exemple, la stack d'un TCP Socket Serveur serait traitée à l'aide de méthodes asynchrones.
Pouvoir assurer un tel  débit de traitement, le pattern "Asynchronous Méthod" doit être implémenté très scrupuleusement afin de maximiser les performance et de maintenir la complexité au minimum.
Joseph Albahari traite ce sujet dans son livre sur C# 3.
Ex: NetworkStream.BeginRead.

7) Asynchronous Event  ou "event-based asynchronous pattern"
Egalement un pattern particulier, son implémentation est idéale pour afficher un rapport de progression ou pour être averti d'un évènement de cancellation. Ce pattern convient idéalement aux applications WinForms ou il se montre plutôt approprié lors de développement de composant.
Ce pattern est approprié lorsqu'il est nécessaire de mette en place des opérations asynchrones que le client peut gérer à l'aide du modèle event/delegate.

  • Basé sur la déclaration d'une xxxxAsync et d'un évènement yyyComplete.Ex: WebClient.DownloadStringAsync et Webclient.DownloadStringComplete
  • La méthode Async fait une exécution asynchrone et appelle l'événement Complete lorsque le traitement est terminé.
  • S'il s'agit d'une pure exécution asynchrone (et non l'implementation d'une notification dans votre propre composant), le même résultat peut être atteint en utilisant les BackgroundWorker.
  • Ne pas utiliser ce pattern si le client de la classe à besoin d'un WaitHandle ou IAsynResult.
Le pattern peut-être implémenté deux façons différents:
  • Pour supporter un seul appel à la fois (non réentrant).
    Dans ce cas, l'évènement xxxComplete doit être exécuté avant de pouvoir refaire un nouvel appel à la méthode xxxAsync.
  • Pour supporter des appels réentrant.
    Il est possible d'appeler plusieurs fois la méthode xxxAsync et il y aura autant d'évènement xxComplete correspondant. Cependant, lors de l'appel il faut passer un objet identificateur (userState aussi appelé TaskId) permettant d'identifier l'appel, l'exécution asynchrone et l'évènement xxxComplete de façon univoque.
Ressource:


8) Timer
Très pratique pour l'implémentation de tâches répétitives, il est important de savoir qu'il existe plusieurs implémentations du Timer dans le Framework .Net. Bien entendu, chacune de ces implémentation à ses propres spécificités.


8.1) Timer - System.Threading.Timer
Fonctionne sur le ThreadPool. Les méthodes callback ne sont donc pas synchronisées avec le thread principal. 
Cette implémentation de Timer convient particulièrement aux routines nécessitant du temps de processing.
Il faut donc utiliser control.Invoke pour toutes les intéractions WinForms.

8.2) Timer - System.Timers.Timer
Founit une implémentation de type composant visuel pour System.Threading.Timer.
Publie donc des propriétés et événement pour facilité le coding de l'application.
Comme il utilise toujours le threadpool, la méthode Callback n'est pas synchrone avec le thread principal.

8.3) Timer - Windows.Forms.Timer
Présente une interface similaire à System.Timers.Timer mais est radicalement différent en ce qui concerne l'implémentation.
Cette implémentation n'utilise pas le threadpool et fonctionne donc de façon synchrone avec le thread principal.
Si cela est un avantage indéniable pour la mise-à-jour de l'interface WinForm le revers de la médaille est de taille.
En effet, ce Timer ne convient pas pour les routines nécessitant beaucoup de ressource car le timer fonctionnant sur le thread principal, il est bloqué durant l'exécution de la méthode callback du timer.
L'exécution de la méthode callback de ce timer doit donc être aussi brève que possible.

    ReadyQueue et WaitQueue
    L'article aborde également le paradigme ReadyQueue et WaitQueue basé sur le wait and pulse.
    Malheureusement, ce paradigme n'est pas des plus faciles a comprendre en quelques mots d'anglais.

    Source: Joseph Albahari
    Pour ce que j'ai compris:
    Ce paradigme permet de coder des ConsumerProducerQueue qui rempli une Waiting Queue (objets en attente d'ajout dans la queue de traitement, généralement poussé par un Thread A) et en alternance, permet de vider la Ready Queue (ces mêmes objets en attente d'extraction pour être traité par un Thread B).


    Ainsi, je recommande également la lecture de l'article "Thread synchronization: Wait and Pulse demystified" de Nicholas Nicholas Butler sur CodeProject.
    Il en démontre l'usage des ReadyQueue et WaitQueue à l'aide de sa classe BlockingQueue.
    Le logiciel crée une BlockingQueue d'integer qu'un thread A remplis pendant qu'un autre thread B vide la queue.

    Wait and Pulse
    C'est une méthode de synchronisation dite non-bloquante.
    En effet, dans les scénarios classique de threading, une synchronisation (par exemple pour accéder à une variable partagée) est dite "bloquante".
    Le thread bloqué (en attente de la synchro) est déschédulé dans l'attente de la libération de la ressource.
    Dans un environnement à fort accès concurrent, cela peut représenter un désavantage car le thread bloqué est retiré pendant un certain temps par le scheduler (alors que la synchronisation pourrait être obtenue dans un délai très court).
    Pour répondre a ce problème spécifique, le framework .Net à mis en place la synchronisation Wait and Pulse permettant à deux (ou plusieurs threads) de partager en même temps le même objet de synchronisation (lock) et de s'avertir mutuellement de la libération de la ressource (en évitant aux threads d'être dé-schedulé).

    L'implémentation d'un Wait And Pulse doit scrupuleusement suivre le pattern.
    En effet, comme les deux threads partagent l'intérieur du même lock ne pas suivre scrupuleusement le pattern causerait inévitablement des problèmes de synchronisations (et beaucoup de cheveux blancs).