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();
 }
        ....
}

Aucun commentaire: