mercredi 13 octobre 2010

Sync C# - Thread Local Storage - utilisation de DataClass

Introduction
L'article "Sync C# - Thread Local Storage - Introduction" expliquait l'utilité et la mise en place d'un TLS anonyme.
L'utilisation de plusieurs slots TLS peut devenir fastidieux a plus forte raison s'il y a beaucoup de données à stocker et à manipuler.
Une alternative élégante est de stocker dans un slot TLS un objet de type "Data" fournissant l'accès aux differentes données via des propriétés (et pourquoi pas, fournir des méthodes de manipulation des données lorsque que c'est approprié).

L'exemple ci-dessous présente cette méthode en stockant un objet de type DataClass dans le slot TLS.

Exemple
Source: Threading_ThreadLocalStorage_DataClass.cs

Cet exemple présente le stockage d'un objet de donnée dans un slot TLS... ainsi que son utilisation dans une des sous-sous-sous fonction de la méthode "Worker" exécutée par le Thread.

La DataClass publie une seule propriété "x" mais cette classe pourrait bien entendu recevoir toutes les autres propriétés utiles à l'exécution de la tâche accomplie par le thread.

Résultats:
Thread:3 DataClass creating
Thread:4 DataClass creating
Thread:3 Set x=9
Thread:3, Loop:0  x:9
Thread:4 Set x=12
Thread:4, Loop:0  x:12
Thread:5 DataClass creating
Thread:5 Set x=15
Thread:5, Loop:0  x:15
Thread:6 DataClass creating
Thread:6 Set x=18
Thread:6, Loop:0  x:18
Thread:7 DataClass creating
Thread:7 Set x=21
Thread:7, Loop:0  x:21
Thread:8 DataClass creating
Thread:8 Set x=24
Thread:8, Loop:0  x:24
Thread:9 DataClass creating
Thread:9 Set x=27
Thread:9, Loop:0  x:27
Thread:10 DataClass creating
Thread:10 Set x=30
Thread:10, Loop:0  x:30
Thread:11 DataClass creating
Thread:11 Set x=33
Thread:11, Loop:0  x:33
Thread:12 DataClass creating
Thread:12 Set x=36
Thread:12, Loop:0  x:36
Press any key to continue...Thread:3, Loop:1  x:10
Thread:4, Loop:1  x:13
Thread:6, Loop:1  x:19
Thread:7, Loop:1  x:22
Thread:5, Loop:1  x:16
Thread:8, Loop:1  x:25
...
Quelques mots d'explication:
  • Le data slot est alloué lors de l'appel du constructeur statique de la DataClass.
    Pour rappel, le slot doit être déclaré une seule fois mais initialisé pour chaque thread.
    Comme il s'agit d'un constructeur statique, il ne sera exécuté qu'une seule fois.
  • Les propriétés de la DataClass sont initialisées lors de l'appel du constructeur non statique.
    Ce constructeur sera normalement appelé pour chacun des threads.
  • La DataClass dispose du getter statique "ThreadedDataClass" qui a pour rôle de retourner une instance de la DataClass.
    Si l'instance de DataClass n'existe pas encore sans le slot TLS, elle est créée à la volée (et donc initialisée avec son constructeur non statique) et stockée dans le slot TLS.
    Sinon, l'instance déjà stockée dans le slot TLS est castée et retournée.
  • Chaque thread utilise uniquement le getter statique DataClass.ThreadedDataClass pour obtenir la référence vers l'objet DataClass allouée pour le thread. c'est magique :-)
  • L'utilisation du TLS est mis en évidence par l'initialisation de la propriété "x" dans le "worker" du thread et sa réutilisation dans la méthode "DoGasp" (après un long chainage d'appel).

Code source:
Source: Threading_ThreadLocalStorage_DataClass.cs
/* This sample demonstrate the usage of *** THREAD LOCAL STORAGE *** to store a DataClass per thread.
   DataClass may be more easy to use but it required to use a static getter on the DataClass.
    
   Each thread receive an isolated data store (which support Messaging, transaction or security token).
   Using LOCAL STORAGE is more appropriate than using method parameter (which can become difficult to maintain).
   Using LOCAL STORAGE is also more appropriate than static field (that share the value for all the threads).

   See PDF "Pratique de .NET 2.0 et de C ♯ 2.0" in french (look for "AllocateDataSlot") that contains a very usefull sample about usage of LocalDataStireSlot.
      http://omega.enstb.org/yannis/pdf/livre2.pdf
   See article "Use LocalDataStoreSlot" on http://www.java2s.com/Tutorial/CSharp/0420__Thread/UseLocalDataStoreSlot.htm
   see article "Use thread-local storage" on http://www.java2s.com/Tutorial/CSharp/0420__Thread/Usethreadlocalstorage.htm
*/
using System;
using System.Collections.Generic;
using System.Threading;

/// <summary>
/// </summary>
public class DataClass 
{
    private static LocalDataStoreSlot tlsSlot = null;
    
    static DataClass() {
        tlsSlot = Thread.AllocateDataSlot(); // Declare the anonymous slot
    }
    
    public DataClass() {
        Console.WriteLine( String.Format( "Thread:{0} DataClass creating", Thread.CurrentThread.GetHashCode()  ) );
        _x = 0;
    }
    
    /// <summary>
    /// Use this method to access the slot from a thread.
    /// If the slot is not yet initialized, the DataClass instance is created and stored in TLS
    /// </summary>
    /// <returns></returns>
    public static DataClass ThreadedDataClass {
        get {
            Object obj = Thread.GetData( tlsSlot );
            if( obj == null ){
                obj = new DataClass();
                Thread.SetData( tlsSlot, obj );
            }
            return (DataClass)obj;
        }
    }
    
    #region DataClass properties 
    private int _x;
    public int x { 
        get {
            return _x;
        } 
        set {
            _x=value;
        } 
    }
    
    #endregion

}

public class MyClass
{
        
    public static void RunSnippet()
    {
        // Create the Threads
        List<Thread> lst = new List<Thread>();
        for( int i = 0; i < 10; i++ ){
              Thread aThread = new Thread( new ThreadStart( Worker ) );
            lst.Add( aThread );
        }        
        
        // Start the threads
        foreach( Thread th in lst )
            th.Start();
    }
    
    public static void Worker(){
        DataClass data = DataClass.ThreadedDataClass;
        DoBlaBla(); // Perform a chaining call to a routine that will modifies the data object
        for( int i = 0; i <= 10; i++ ) {            
            Console.WriteLine( String.Format( "Thread:{0}, Loop:{1}  x:{2}", Thread.CurrentThread.GetHashCode(), i, data.x ) );
            data.x = data.x + 1;
            Thread.Sleep( 50 ); // Help in Context Switching
        }
    }
    
    public static void DoBlaBla()
    {
        DoSnapSnap();
    }
    
    public static void DoSnapSnap()
    {
        DoBlurpBlurp();
    }

    public static void DoBlurpBlurp()
    {
        DoGasp();
    }
    
    public static void DoGasp()
    {        
        DataClass data = DataClass.ThreadedDataClass;
        data.x = Thread.CurrentThread.GetHashCode()*3;
        Console.WriteLine( String.Format( "Thread:{0} Set x={1}", Thread.CurrentThread.GetHashCode(), data.x ) );
    }
    
    #region Helper methods
    
    public static void Main()
    {
        try
        {
            RunSnippet();
        }
        catch (Exception e)
        {
            string error = string.Format("---\nThe following error occurred while executing the snippet:\n{0}\n---", e.ToString());
            Console.WriteLine(error);
        }
        finally
        {
            Console.Write("Press any key to continue...");
            Console.ReadKey();
        }
    }

    private static void WL(object text, params object[] args)
    {
        Console.WriteLine(text.ToString(), args);    
    }
    
    private static void RL()
    {
        Console.ReadLine();    
    }
    
    private static void Break() 
    {
        System.Diagnostics.Debugger.Break();
    }

    #endregion
}

Aucun commentaire: