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).



      samedi 25 septembre 2010

      Comment reconstituer un Raid 5 ayant 2 disques défectueux

      Voici une petite histoire collectée au service technique.
      C'est celle d'une serveur Windows Serveur avec un Raid 5 dont 2 des 3 disques étaient défectueux.
      Pour rappel, lorsque l'on perd un disque en Raid 5, il est possible de reconstituer l'information perdue.
      Mais dans le cas ci-présent, c'est plus gênant car normalement, tout est théoriquement perdu.

      Ce que j'ai appris d'intéressant, c'est que disque Raid peut être réjeté de l'array (l'ensemble des disques constituant le volume Raid 5) parce qu'il est devenu suspect. Par exemple, parce qu'il contient des secteurs corrompus parce que le disque commence à defaillir.
      Cela ne veux pas dire pour autant que la totalité des informations sur le disque éjecté sont devenu inutilisables.

      Ainsi, sur base des informations fournies par Yves (merci Yves) j'ai appis qu'il était possible de récupérer la quasi totalité des données (si pas la totalité) dans un cas aussi extrême... Tout dépendant alors où se trouvent les secteurs défectueux.

      Cas de figure
      Soit un Windows Serveur avec un volume Raid 5 ayant les caractéristiques suivantes:
      • Disque 1 - OK
      • Disque 2 - BAD (éjecté du volume Raid 5)
      • Disque 3 - BAD (éjecté du volume Raid 5)
      Dans ce cas de figure, le volume Raid 5 ne peut même plus être activé au démarrage.
      Comme le système d'exploitation est placé sur le volume Raid, le système d'exploitation ne démarre même plus (en fait, la machine ne trouve même plus de disque à booter).

      Marche a suivre1) Arrêter la machine et débrancher le controlleur raid (la carte) et retirer les disques des baies.
      2) Installer un disque IDE et booter dessus.
      3) Reinstaller Windows Server sur le disque dur IDE + le logiciel du contrôleur Raid (fournit par le constructeur de la contrôleur Raid) +Shutdown.
      4) Réinstaller le controlleur Raid + le Disque 1 (OK) + le Disque 2 (BAD) + le Disque 3 (BAD)
      5) Rebooter la machine en IDE.
      Windows va détecter un Raid (instable). Comme les Disque 2 et Disque 3 sont corrompus, ils seront éjectés du volume Raid. En conclusion, Windows ne retrouve pas le Volume Raid (c'est comme s'il n'existait pas).
      6) Réactiver le Disque 2
      Pour ce faire, utiliser le logiciel du constructeur du contrôleur Raid. La plupart de ces logiciels permettent de forcer la réinclusion d'un disque suspect dans le volume Raid.
      En faisant cette opération, nous avons le nombre de disque minimum pour reconstituer un Volume Raid viable (bien que le Disque 2 soit défectueux).


      7) Faire un CheckDisk pour repérer et réparer les secteurs défectueux du disque 2.
      8) Placer un disque vierge pour remplace le Disque 3 (nommons le Disque Tr) et reconstituons le Raid.
      A cette étape, nous avons un Raid 5 conforme avec les disques.
      • Disque 1 - OK
      • Disque 2 - BAD (mais CheckDisk)
      • Disque Tr - OK
      Comme disque 2 est de toute façon défectueux, il ne tardera probablement pas a présenter de nouvelles erreurs.
      Sans opérations complémentaires, le Raid ne tardera probablement pas a redevenir de nouveau non opérationnel. Le plus simple étant encore de prendre le taureau pas les cornes (voir point suivant).

      9) Retirer le disque 2 et le remplacer par un disque vierge (nommons le Disque De) et reconstruire encore une fois le Raid.
      Après cette deuxième reconstitutions du Raid, nous avons les disques suivants:
      • Disque 1 - OK
      • Disque De - OK
      • Disque Tr - OK
      Le Raid est reconstitué avec des disques fiables mais plus que probablement avec quelques fichiers corrompus (cfr opération de CheckDisk).
      S'il est ennuyeux de perdre quelques fichiers il aurait été plus dramatique de perdre l'entièreté des disques.
      Mais avec de la chance, L'OS n'est peut être même pas toucher et un peu plus de chance, aucuns fichiers importants.

      10) Eteindre la machine et retirer le Disque IDE.
      11) Rebooter la machine sur le Raid

      Selon Yves (détenteur de cette recette de cuisine particulière), il a déjà l'occasion d'appliquer cette méthode 3 fois en récupérant les OS et les informations vitales.

      vendredi 24 septembre 2010

      Helper - Find Column

      Il n'y a pas si longtemps que cela, je cherchais les tables contenant les prix d'achat (BuyPrice).
      Malheureusement, la DB contient beaucoup de tables et beaucoup de champs.
      De surcroit, le nom du champ n'est pas obligatoirement exactement "BuyPrice" car il peut contenir préfix et suffix (ex: CreditBuyPrice, BuyPriceTotal, etc).
      L'opération s'annonçait donc fastidieuse.

      Heureusement, il existe la table Sys.SysColumns pour nous faciliter la vie et un petit script SQL peut rendre la tâche bien plus agréable.

      --  
      --  Helper - Find Column
      --
      -- Description:
      --    Locate Tables having a columnName looking to a string.
      --
      --    @PartialColumnName contains the columns name to look for.
      --    @OnlyTables        1: Only user tables, 
      --                       0: also includes views, table valued function (parameter & returned columns), etc
      --
      -- Author: Meurisse Dominique
      -- Changes:
      --   20/09/2010 - creation
      --   20/09/2010 - Add parameter @OnlyTables
      --
      DECLARE @PartialColumnName varchar(20)
      DECLARE @OnlyTables smallint 
      
      SET @PartialColumnName = 'BuyPrice' -- <<<< change here >>>>
      SET @OnlyTables = 1 -- <<< Look only on tables or All object >>>
      
      select SCHEMA_NAME( uid )+'.'+obj.name as TableName, col.name as ColumnName 
      from sys.syscolumns col 
      inner join sys.sysobjects obj on obj.id = col.id and (@OnlyTables = 0 or (@OnlyTables = 1 and obj.xtype = 'U'))
      where LOWER(col.name) like '%'+LOWER(@PartialColumnName)+'%'
      order by SCHEMA_NAME( uid ), obj.name
      

      vendredi 17 septembre 2010

      Encodage de MP3 avec Audacity sous Ubuntu

      Audacity est un logiciel libre et open-source destiné à l'édition et à l'enregistrement audio.
      Il est disponible sur Ubuntu via la logithèque.

      Comme bien des logiciels libres, Audacity utilise les libraires de Lame pour l'encodage de fichier MP3.
      Cette librairie d'encodage n'étant habituellement pas distribuée car elle entre inévitablement en conflit avec les différentes licences libres (en effet, l'encodage MP3 n'est pas un algorithme open-source, ce qui peut entrer en conflit avec le système de licence du logiciel distribué).

      Lors de l'exportation MP3 avec Audacity, on tombe sur un charmant message stipulant que la librairie libmp3lame0.so est manquante sur la distribution Ubuntu.
      Pour l'installer, il suffit d'utiliser la commande suivante:
      sudo apt-get install libmp3lame0
      

      mercredi 8 septembre 2010

      JavaScript n'est pas mort

      Si certains en doute, JavaScript a encore de beaux jours devant lui.
      Langage de base pour  l'exécution de code sur les pages internet, ce langage ne cesse pas d'évoluer.
      Il gagne en rapidité et en légèreté. Yahoo, Google et bien d'autres acteurs lui ont offert des framework vraiment impressionnant.
      L'avènement de HTML 5 avec de ses nouvelles balises multimédia, la gestion des objets 2D et 3D  et les besoins multimédia sans cesse plus important renforce encore la prédominance de langage qui est, faut-il le rappeler, aussi vieux que le Web.

      En furetant un peu, je suis tombé sur cette vidéo qui démontre l'utilisation d'une nouvelle API Audio Data de Firefox.
      L'exploitation de cette API est bien évidement en JavaScript... et le résultat "Impressionnant".



      Ce sera également l'occasion de profiter d'une bande son "Revolve" His-Boy_Elroy qui est assez sympa.
      D'autres titres sont d'ailleurs disponible sur le site internet de His Boy Elroy.

      mardi 7 septembre 2010

      Comment identifier les query couteux en CPU

      L'utilisation des Dynamic Management Views permettent d'obtenir des informations bien utiles.
      C'est ainsi que la requête suivante identifie les requêtes sql les plus couteuses (par cpu) parmis celles enregistrées dans le cache.


      SELECT TOP 20
          qs.sql_handle,
          qs.execution_count,
          qs.total_worker_time AS Total_CPU,
          total_CPU_inSeconds = --Converted from microseconds
              qs.total_worker_time/1000000,
          average_CPU_inSeconds = --Converted from microseconds
              (qs.total_worker_time/1000000) / qs.execution_count,
          qs.total_elapsed_time,
          total_elapsed_time_inSeconds = --Converted from microseconds
              qs.total_elapsed_time/1000000,
         st.text,
         qp.query_plan
      from
          sys.dm_exec_query_stats as qs
          CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) as st
          cross apply sys.dm_exec_query_plan (qs.plan_handle) as qp
      ORDER BY qs.total_worker_time desc
      


      Cette requête retourne la requête Sql (inclus stored proc) mais également l'exécution plan!
      Source:

      Sql Serveur 2005 Backups

      Juste une petite révision des différentes méthodes de backup sur sql serveur (parce que cela fait longtemps).
      Voir l'article SQL Server 2005 Backups.