Soluzione di back-end scalabile basata su Thread Pool - .NET 4.0

Segue i precedenti post e post dove illustro una soluzione scalabile per l'implementazione di una procedura di elaborazione di back-end con utilizzo del thread pool.

Soluzione basata su Thread Pool con .NET Framework 4.0

Ho realizzato la soluzione basata il .NET Framework 4.0 utilizzando le nuove classi disponibili nel namespace System.Threading.Tasks. Il problema della schedulazione è stato facilmente risolto in quanto ho ereditato una soluzione ottimale direttamente dal sito msdn basata su questo articolo How to: Create a Task Scheduler That Limits the Degree of Concurrency nel quale è disponibile una classe di esempio che estende la classe TaskScheduler e implementa il controllo del limite di concorrenza dei task eseguti nel pool.

La versione sottoclasse di TaskNet che implementa l'esecuzione effettiva che si occupa di avviare i task risulta relativamente semplice, essendo costruita sulle nuove classi System.Threading.Tasks, ma soprattuto non richiede impegno aggiuntivo per scrivere classi particolari di sincronizzazione.

    public class EngineThreadPool2 : Engine

    {

        ...

        private readonly CancellationTokenSource cancellationSource = new CancellationTokenSource();

        ... 

        protected override void MainLoop()

        {

            TaskScheduler taskScheduler = null;

 

            if (numThreads.HasValue)

                taskScheduler = new LimitedConcurrencyLevelTaskScheduler(numThreads.Value);

 

            TaskFactory factory = new TaskFactory(taskScheduler);

 

            List<Task> enqued = new List<Task>();

 

            foreach (TaskFakeMixed task in TaskProvider.Tasks)

            {

                TaskFakeMixed taskToExecute = task;

 

                enqued.Add(factory.StartNew(() =>

                                                {

 

                                                    taskToExecute.ProcessTask();

                                                    UpdateStatistics(taskToExecute);

                                                }, cancellationSource.Token));

            }

 

            try

            {

                Task.WaitAll(enqued.ToArray(), cancellationSource.Token);

            }

            catch (OperationCanceledException)

            {

            }

        }

        ... 

        protected override void RequestStopTasks()

        {

            cancellationSource.Cancel();

        }

        ...

    }

Notare l'uso della classe TaskFactory la quale prevede un costruttore che accetta una classe TaskScheduler, nel caso sia passatto null viene utilizzato lo scheduler di default. La creazione del Task avviene attraverso il metodo TaskFactory.StartNew(), in questo caso è stato utilizzato l'overload che prevede in ingresso un Action, delegato che contiene il corpo del task, e un oggetto di tipo CancellationTokenSource che permette di notificare la cancellazione dell'esecuzione al task. Una volta che tutti i task sono stati creati e inseriti nel pool per l'esecuzione, il thread viene messo in attesa attraverso il metodo Task.WaitAll(...) al quale viene passato il vettore dei task e il CancellationToken per interrompere l'esecuzione. Quando l'utente decide di fermare il processo la form chiama il metodo RequestStopTasks() dell'engine che a sua volta invoca CancellationTokenSource.Cancel() il quale comunica la richiesta di cancellazione al token, e fa generare l'eccezione OperationCanceledException a Task.WaitAll(...) sbloccando l'esecuzione.

Soluzione alternativa con l'uso delle Parallel Extensions del .NET Framework 4.0

Una soluzione alternativa è quella di utilizzare le estensioni per l'esecuzione parallela, cosidette Parallel Extensions, di cui ci omaggia il .NET Framework 4.0. In questo caso il cuore del motore di schedulazione, la funzione Engine.MainLoop(), risulta molto semplice, di seguito riporto il codice.

    public class EngineParallelExt : Engine

    {

       ... 

        protected override void MainLoop()

        {

            if(numThreads.HasValue)

            {

                ParallelOptions options = new ParallelOptions { MaxDegreeOfParallelism = numThreads.Value };

                Parallel.ForEach(TaskProvider.Tasks, options, (task, state) => ExecuteTask(state, task));

            }

            else

            {

                Parallel.ForEach(TaskProvider.Tasks, (task, state) => ExecuteTask(state, task));

            }

        }

 

        private void ExecuteTask(ParallelLoopState state, TaskFakeMixed task)

        {

            task.ProcessTask();

 

            UpdateStatistics(task);

 

            if (IsRequestStop)

                state.Break();

        }

       ... 

    }

La funzione chiave è il metodo statico Parallel.ForEach dotato di numerosi overloads, in particolare nella soluzione proposta, se è stato specificato un limite di parallelismo, viene utilzzata anche un istanza della classe ParallelOptions che ci mette a disposizione una proprietà MaxDegreeOfParallelism per definire il massimo parallelismo da applicare nell'esecuzione dei task, altrimenti le Task Parallel Library utilizzano il modello di partizione e le opzoni predefinite (vedi Data Parallelism (Task Parallel Library)). L'overload utilzzato è quello che prevede la collezione di elementi e un Action con due parametri, questi ultimi sono un ParallelLoopState e un TSource che rappresenta il tipo di item della collezione di origine. L'oggetto di tipo ParallelLoopState viene utilizzato per terminare l'esecuzione dell'elaborazione parallela attravereso il metodo Break().

Indice post - Soluzione di back-end scalabile basata su Thread Pool

Introduzione
Soluzione .NET 3.5

Soluzione di back-end scalabile basata su Thread Pool - .NET 3.5

Segue il precedente post dove illustro una soluzione scalabile per l'implementazione di una procedura di elaborazione di back-end con utilizzo del thread pool.

Form principale

La form principale si occupa di costruire e inizializzare l'engine e avviarne l'esecuzione, l'engine viene eseguito in un thread e comunica con la form principale attraverso eventi asincroni (vedi precedente post Sincronizzare un applicazione Windows Forms con un thread separato definendo un servizio astratto). Il thread dell'engine principale rimane in esecuzione finchè tutte le attività non sono state processate oppure avviene un eccezione inaspettata.

L'engine dispone di un entry-point denominato Start() che avvia il thread di esecuzione principale (2o livello descritto nel post precedente).

    public abstract class Engine

    {

        ...

        public void Start()

        {

            thread = new Thread(StartThread);

            thread.Start();

        }

 

        private void StartThread()

        {

            requestStop = false;

 

           try

           {

              NotifyStartInfo();

              StartEngine();

              InitStatistics();

              MainLoop();

              NotifyEndInfo();

           }

           catch (Exception exception)

           {

              lastRunException = exception;

           }

 

            InvokeOnSchedulerTerminated();

        }

 

        protected abstract void MainLoop();

       ... 

}

Come si può vedere la classe Engine è astratta, in particolare il metodo MainLoop() della sotto classe deve implementare il ciclo principale, che nella sostanza è un algoritmo di tipo produttore/consumatore, dove, nel nostro caso il produttore genera i task di elaborazione mentre il consumo dei task avviene nel thread pool.

Soluzione basata su Thread Pool con .NET Framework 3.5

Innanzitutto per implementare la soluzione con il .NET Framework 3.5 ho realizzato alcune classi Helper, la più importante è la classe denominata TaskNet35, la quale fornisce metodi per creare, avviare e sincronizzarsi con un Task messo in coda per l'esecuzione nel Thread Pool (Per approfondimenti vedi Programming the Thread Pool in the .NET Framework). Di seguito ne riporto le parti più importanti.

    /// <summary>

    /// Rappresenta un task da eseguire

    /// </summary>

    public abstract class TaskNet35

    {

        public static int MaxWaitHandlersCount = 64;

 

        public event EventHandler TaskFinished;

 

        private ManualResetEvent finishedEvent = new ManualResetEvent(false);

        private bool requestStop = false;

        private bool finished;

        private bool started;

        private Exception taskException;

 

        ... 

        public void Start()

        {

            started = true;

 

            ThreadPool.QueueUserWorkItem(InternalTaskOperation);

        }

 

        private void RequestStop()

        {

            requestStop = true;

        }

 

        private void InternalTaskOperation(object state)

        {

            if (requestStop)

            {

                SignalTaskFinished();

                return;

            }

 

            try

            {

                TaskOperation();

            }

            catch (Exception exception)

            {

                taskException = exception;

            }

 

            SignalTaskFinished();

            InvokeTaskFinished();

        }

 

        private void SignalTaskFinished()

        {

            finished = true;

            finishedEvent.Set();

        }

 

        /// <summary>

        /// Attività del task da implementare nella sottoclasse

        /// </summary>

        protected abstract void TaskOperation();

 

        /// <summary>

        /// Attende che uno qualsiasi dei task passati siano terminati

        /// </summary>

        public static void WaitAny(TaskNet35[] tasks)

        {

            ManualResetEvent[] handles = (from task in tasks select task.finishedEvent).ToArray();

            if (handles.Length > 0)

                WaitHandle.WaitAny(handles);

        }

 

        /// <summary>

        /// Attende che tutti i task passati siano terminati

        /// </summary>

        public static void WaitAll(TaskNet35[] tasks)

        {

            ManualResetEvent[] handles = (from task in tasks select task.finishedEvent).ToArray();

 

            if (handles.Length > 0)

                WaitHandle.WaitAll(handles);

        }

       ... 

    }

Come si può vedere la classe fornisce due metodi statici WaitAll e WaitAny per sincronizzarsi con il vettore di Task passati, il primo metodo rimane in attesa fino a quando tutti i task del vettore sono terminati, il secondo invece rimane in attesa l'esecuzione fino a quando almeno uno dei task in esecuzione è terminato. Per implementare la sincronizzazione ogni istanza di TaskNet35 incapsula un oggetto ManualResetEvent, un evento inizializzabile manualmente per la sincronizzazione multithread. Inoltre il metodo Start() inserisce nella coda del Thread Pool il task attraverso il metodo statico QueueUserWorkItem la cui implementazione è rappresentata dalla funzione InternalTaskOperation(), importante è la chiamata di finishedEvent.Set() che imposta a true lo stato dell'oggetto di sincronizzazione e quindi sblocca l'eventuale thread in attesa. Inoltre la classe implementa un sistema di annullamento dell'esecuzione, attraverso il campo requestStop di tipo bool e la chiamata al metodo RequestStop(), questo approccio prevede che se il task è stato inserito nella coda del thread pool e non è ancora stato eseguito, quest'ultimo termina immediatamente.

Successivamente ho implementato una classe denominata TaskPoolNet35 che fa da "scheduler" per l'avvio dei Task controllando la concorrenza di esecuzione dei task qui di seguito riporto tutto il codice della classe essendo relativamente semplice.

    /// <summary>

    /// Limita l'esecuzione della concorrenza di task nel Thread pool.

    /// </summary>

    public class TaskPoolNet35

    {

        private readonly TaskNet35[] tasks;

 

        public TaskPoolNet35()

        {

            tasks = new TaskNet35[TaskNet35.MaxWaitHandlersCount];

        }

 

        public TaskPoolNet35(int numThreads)

        {

            tasks = new TaskNet35[numThreads];

        }

 

        public void AddWaitStartTask(TaskNet35 task)

        {

            for (int idx = 0; idx < tasks.Length; idx++)

            {

                if(tasks[idx]==null || tasks[idx].Finished)

                {

                    tasks[idx] = task;

                    task.Start();

                    return;

                }

            }

 

            TaskNet35.WaitAny(tasks);

            AddWaitStartTask(task);

        }

 

        public void WaitAll()

        {

            bool containsAlmostOneTask = tasks.Any(t => t != null);

 

            if (containsAlmostOneTask)

                TaskNet35.WaitAll(tasks.ToArray());

        }

 

        public void StopAll()

        {

            bool containsAlmostOneTask = tasks.Any(t => t != null);

 

            if (containsAlmostOneTask)

                TaskNet35.StopAll(tasks.ToArray());

        }

    }

Come si può vedere il core è la funzione AddWaitStartTask() che ha due compiti, inserire il task in un vettore ed eseguirlo e quando il vettore è pieno attendere che un task si liberi per inseirlo nel vettore ed eseguirlo. Usando opportunamente la ricorsività la funzione AddWaitStartTask() risulta molto compatta. Nel costruttore della classe TaskPoolNet35 deve essere indicato il parallelismo voluto, se non indicato si assume sia 64, che è il limite massimo di Handle sincronizzabili con le funzioni WaitHandle.WaitAll(..) e WaitHandle.WaitAny(..).

A questo punto, è sufficiente implementare la sottoclasse di TaskNet che implementa l'esecuzione dell'operazione effettiva (nell'esempio sottoriportato MyTaskNet35 è sottoclasse di TaskNet35 nel cui costruttore viene passato come parametro una classe nota denominata TaskFakeMixed che fornisce un motodo ProcessTask() il quale implementa il task effettivo, in alternativa si può implementare la logica del task direttamente nella sottoclasse, oppure passare un delegato nel costruttore della sottoclasse come avviene ad esempio con la classe Task del .NET Framework 4.0). Il MainLoop() dell'engine risulterà relativamente semplice.

        protected override void MainLoop()

        {

            taskPool = numThreads.HasValue ? new TaskPoolNet35(numThreads.Value) : new TaskPoolNet35();

 

            foreach (TaskFakeMixed task in TaskProvider.Tasks)

            {

                TaskFakeMixed taskToExecute = task;

                MyTaskNet35 myTaskNet35 = new MyTaskNet35(taskToExecute);

                myTaskNet35.TaskFinished += myTaskNet35_TaskFinished;

 

                //Add task wait if current running tasks concurrency > numThreads

                taskPool.AddWaitStartTask(myTaskNet35);

 

                if (IsRequestStop)

                    break;

            }

 

            //Wait all tasks

           taskPool.WaitAll();

 

       }

 

Importante notare il meccanismo di "uscita" nel caso di annullamento dell'esecuzione, questo si limita al controllo della proprietà IsRequestStop, in quanto l'esecuzione è già sincronizzata e limitata nel parallelismo dal metodo AddWaiStartTask(...), il quale mette in attesa il thread dell'Engine quando il vettore di task in esecuzione è completo. La chiamata a WaitAll() al termine è fondamentale per aspettare che tutti i task in coda siano eseguiti prima di restituire il controllo al chiamante e quindi terminare il thread di esecuzione del motore.

Nel prossimo post illusterò la medesima soluzione con l'ausilio dei "facilitatori" forniti dal .NET Framerwork 4.0, in particolare illustrerò una soluzione equivalente, e una soluzione alternativa utilizzando le parallel extension.

Indice post - Soluzione di back-end scalabile basata su Thread Pool

Introduzione

Soluzione di back-end scalabile basata su Thread Pool

Questo post è basato su un problema reale che ho dovuto affrontare, l'obbiettivo era implementare una soluzione scalabile per un processo di elaborazione (back-end) di un software gestionale. La soluzione è stata costruita evolvendo l'esperienza pregressa sullo specifico requisito insieme a quella relativa a soluzioni multi-thread applicate a questo tipo di processi. Visto che l'architettura progettata e realizzata si è rivelata efficace voglio condividere aspetti specifici della soluzione.

Doverosa premessa

Chi ha esperienza nello scrivere soluzioni multi-thread sa bene che queste ultime non sono un vaso di pandora, per cui se io decido di usare i thread allora la mia applicazione è più veloce e scalabile. Ogni problema va attentamente studiato perchè può capitare che la strada del multi-thread, oppure la modalità con cui viene applicata, non sia la soluzione migliore ma anzi si riveli sbagliata. Questa premessa è doverosa perchè la soluzione proposta sia vista nella giusta ottica, cioè calata nel problema specifico (o nelle classe dei problemi) per cui è stata pensata e realizzata.

Contorni del problema

In sintesi il quesito era questo: Abbiamo a disposizione un servizio esterno costruito su filosofia SOA (Service Oriented Architecture) ed implementato attraverso l'esposizione di un servizio WEB basato su protocollo SOAP. Il servizio serve per l'archiviazione sostitutiva di documenti digitali. Ogni documento da archiviare richiede la chiamata di un metodo sul servizio esterno e corrisponde ad una transazione. La singola attività del processo è di fatto distribuita e possiamo scomporla come composta da tre fasi distinte:

  • Preparazione del documento (sistema locale)
  • Archiviazione sostitutiva (servizio remoto)
  • Registrazione esito (sistema locale)

Il servizio esterno consuma la singola richiesta in un tempo relativamente basso ma significativo (mediamente un secondo). Per via delle specifiche caratteristiche dell'architettura tecnologica, il servizio è in grado di processare più richieste contemporaneamente senza riduzione delle prestazioni ma entro un livello massimo di richieste parallele, oltre il quale le prestazioni calano significativamente. La preparazione del documento ha un costo significativo in termini di elaborazione del sistema locale se rapportato alla registrazione dell'esito che è semplicimente il salvataggio di una ricevuta, ma comunque di un ordine di grandezza inferiore rispetto al tempo richiesto per la chiamata del servizio esterno.

Tenendo conto di questi presupposti il requisito era di riscrivere la procedura ottimizzandone le prestazioni. Questo richiedeva di realizzare una soluzione che garantisse maggiore scalabilità del processo, supportando l'elaborazione parallela di più documenti per la conservazione sostitutiva.

Architettura della soluzione

Prima di pensare a qualunque soluzione, un aspetto molto importante su cui è stato necessario prestare attenzione sono le caratteristiche della singola attività da processare. In particolare il tipo di task descritto si può scomporre nelle seguenti macro-fasi: 

  • Accesso in lettura all'archivio
  • Preparazione del dato per il trasferimento
  • Chiamata del servizio esterno
  • Registrazione sull'archivio dell'esito

Di queste fasi l'unica veramente significativa in termini di carico CPU è la seconda la quale prevede una serie di operazioni CPU intensive per la preparazione del dato, le restanti hanno un rapporto bilanciato e dipendono soprattutto dalle prestazioni del file system e/o database e/o della rete locale, nella chiamata del servizio esterno, invece, è significativo il tempo di attesa sincrono, in cui il thread di esecuzione del metodo non fa niente, aspettando la risposta del sistema remoto. Di seguito riporto uno schizzo dell'andamento temporale della singola attività con evidenziate le fasi di carico significativo di CPU.

Diagramma esemplificativo carico attività

Le peculiari caratteristiche dell'attività mi hanno portato a scegliere un architettura basata su thread pool, dato che i task si prestano bene ad essere parallelizzati con un fattore dipendente dai limiti della piattaforma locale e del servizio esterno. Dopo attente valutazioni e confronti con i miei colleghi sono arrivato a formulare un architettura a tre livelli, il thread principale dell'applicazione (1), un worker thread, che si occupa delle fasi di inizializzazione, avvio, controllo del processo e generarezione dei task (2), i quali vengono eseguiti nel thread pool (3).

Diagramma esemplificativo architettura

Il primo vantaggio di questa soluzione, nel caso il processo sia scomponibile in task indipendenti, è il basso livello di sincronizzazione richiesto, in pratica l'engine principale, che ho chiamato per convenzione Schedulatore, invia eventi asincroni all'interfaccia grafica per notificare segnalazioni, statistiche e termine del processo. Nel caso dei task l'uso degli eventi non è obbligatorio a meno che il processo sia composto da più tipologie di task per oggetto da elabarore o a fini di controllo o statistiche particolari, allora in questo caso può essere necessario generare ed intercettare eventi, tipicamente di termine del task. Quest'archiettura permette così di mantere basso il numero di lock. Un altro vantaggio importante è che è possibile e facile configurare e controllare il livello di parallelismo dei task all'interno del thread pool.

Aspetti tecnici della soluzione

Nei successivi post illustrerò alcuni passaggi principali della soluzione implementata su un prototipo che sto riscrevendo in VS 2010 con il .NET framework 4.0, la soluzione effettiva è stata implementata con il .NET framework 3.5 il quale pur avendo tutti gli strumenti per poter lavorare con il thread pool, non dispone di facilatori come la classe Task o le Parallel Extension. La mia idea è di illustrare le parti fondamentali della soluzione confrontando le alternative evolute perchè è probabile che, come nel mio caso non potete già utilizzare il .NET 4.0 per vincoli del vostro progetto ma vi farebbe piacere valutare in prospettiva le alternative tecniche offerte dal nuovo framework.

Enum, DescriptionAttribute e Binding

Il tipo enum, come descrive il suo nome, è un enumeratore di valori costanti. L'enum aggiunge anche espressività al nostro codice, e a volte è pratico utilizzarlo anche in presenza di un attributo o un parametro che può assumere solo due valori piuttosto che utilizzare il "semplice" tipo bool. Di seguito riporto alcuni esempi di codice che uso per estendere le informazioni del tipo enum, per convertirlo in una tupla di valori e per utilizzarlo in un controllo ComboBox.

E' possibile utilizzare l'attributo [Description(description)] per associare una descrizione ad ogni valore dell'enumeratore:

public enum TestEnum

{

  Value1,

   [Description("This is value 2")]

  Value2

}

Definendo un opportuno Extension Method è possibile aggiungere un nuovo metodo GetDescription() al valore dell'enum per estrarre la descrizione, se disponibile perchè definito l'attributo [Description("...")]:

public static class UtilsEnum

{

  public static string GetDescription(this Enum en)

  {

    Type type = en.GetType();

 

    MemberInfo[] memInfo = type.GetMember(en.ToString());

 

    if (memInfo != null && memInfo.Length > 0)

    {

      DescriptionAttribute descriptionAttribute =

      Attribute.GetCustomAttribute(memInfo[0], typeof (DescriptionAttribute), true) as DescriptionAttribute;

 

      if (descriptionAttribute!=null)

        return descriptionAttribute.Description;

    }

 

    return en.ToString();

  }

}

Ecco come funziona il metodo visto attraverso due unit test:

[TestClass]

public class UtilsEnumTest

{

   [TestMethod]

  public void WhenEnumHasNotDescriptionAttributeGetDescriptionReturnValueAsString()

   {

    Assert.AreEqual("Value1", TestEnum.Value1.GetDescript());]

   }

 

   [TestMethod

  public void WhenEnumHasDescriptionAttributeGetDescriptionReturnDescription()

  {

    Assert.AreEqual("This is value 2", TestEnum.Value2.GetDescription());

  }

}

A questo punto è possibile scrivere un metodo per convirtire un tipo enum in una Lista di tuple valore-descrizione, funzione utile per poter effettuare il binding dell'enumeratore su controlli di interfaccia utente:

public static class UtilsEnum

{

  public static List<Tuple<ENUM_TYPE,string>> GetEnumTupleValues<ENUM_TYPE>()

  {

    List<Tuple<ENUM_TYPE,string>> tuples = new List<Tuple<ENUM_TYPE, string>>();

 

    foreach (object value in Enum.GetValues(typeof (ENUM_TYPE)))

      tuples.Add(new Tuple<ENUM_TYPE, string>((ENUM_TYPE) value, ((Enum) value).GetDescription()));

 

    return tuples;

  }

}

Di seguito un test che illustra il funzionamento del metodo GetEnumTupleValues<ENUM_TYPE>(): (Nota: UtilsCollection.AreEquals è un metodo di utility che confronta due liste (generics List<T>) e ritorna true se sono uguali)

[TestClass]

public class UtilsEnumTest

{

  [TestMethod]

  public void EnumTuplesContainAllEnumValuesAndDescription()

  {

    List<Tuple<TestEnum, string>> expected = new List<Tuple<TestEnum, string>>()

                                       {

                                       new Tuple<TestEnum, string>(TestEnum.Value1,

                                                                   TestEnum.Value1.GetDescription()),

                                       new Tuple<TestEnum, string>(TestEnum.Value2,

                                                                   TestEnum.Value2.GetDescription())

                                       };

 

     List<Tuple<TestEnum, string>> tuples = utils.UtilsEnum.GetEnumTupleValues<TestEnum>();

 

     Assert.IsTrue(UtilsCollection.AreEquals(expected, tuples));

  }

}

Ecco quindi un uso tipico in una applicazione Windows Forms, dove è stato inserito un combo-box e una label che visualizza la scelta effettuata, le opzioni della combo sono quindi alimentate da un enumeratore.

Per effettuare il binding del combo-box è sufficiente assegnare alla proprietà DataSource le tuple generate con la funzione GetEnumTupleValues<ENUM_TYPE>() e quindi impostare le proprietà ValueMember e DisplayMember rispettivamente a "Item1" e "Item2" che corrispondono alle proprietà esposte dall'interfaccia della classe Tuple<T1,T2>. Per sapere il valore corrispondente dell'enum selezionato sul combo-box è sufficente utilizzare la proprietà SelectedValue la quale, può essere convertita al tipo dell'enumeratore. Di seguito il codice dell'applicazione di esempio:

public partial class Form1 : Form

{

  private void Form1_Load(object sender, EventArgs e)

  {

    cmbEnumTest.DataSource = UtilsEnum.GetEnumTupleValues<Opzioni>();

    cmbEnumTest.ValueMember = "Item1";

    cmbEnumTest.DisplayMember = "Item2";

  }

 

  public enum Opzioni

  {

    [Description("Prima opzione")]

    OpzioneA,

    [Description("Opzione avanzata")]

    OpzioneB,

    [Description("Opzione semplice")]

    OpzioneC,

    [Description("Ultima opzione")]

    OpzioneZ

  }

 

  private void cmbEnumTest_SelectedIndexChanged(object sender, EventArgs e)

  {

    lblSelected.Text = cmbEnumTest.SelectedValue.ToString();

  }

}

Programmatori molto umili - "Very Humble Programmers"

Questo post è ispirato al documento " The Humble Programmer" (l'umile programmatore) scritto da Edsger W. Dijkstra, ricercatore e informatico olandese. In questo documento, Dijkstra fa una riflessione realistica e interessante della sua esperienza come programmatore avendo iniziato in un periodo dove la programmazione dei "calcolatori" veniva percepita in modo riduttivo e semplicistico. ... to become....., yes what? A programmer? But was that a respectable profession? For after all, what was programming? Where was the sound body of knowledge that could support it as an intellectually respectable discipline? ...

Forse è un tema consumato, ma penso che l'approccio alla programmazione nel nostro lavoro sia molto importante, quanto continuare ad acquistare conoscenza, competenza e metodo nello sviluppo del software. Nel corso di questi anni di pratica della mia professione non ho potuto fare a meno che notare come l'atteggiamento umile sia spesso correlato ad una migliore competenza, collaborazione e risultati nel proprio lavoro. Posso dire che i migliori programmatori che ho conosciuto sono persone umili, oneste e concrete.

A volte capita di avere a che fare con persone che si mantengono sulla difensiva, cercando sempre di giustificare i loro errori o peggio dando la colpa ad altri, purtroppo questo a scapito della loro crescita professionale, del gruppo di lavoro e del progetto stesso. La questione è semplice il nostro è un lavoro difficile più di quanto, spesso, ne abbiamo cognizione, ecco perché è importante l'umiltà, nell'eccezione del mantenere i piedi per terra, riconoscendo i propri limiti, mettendosi in discussione, ed affrontando i problemi con metodo e per piccoli passi.

Nella realtà Dijkstra ha verificato su stesso come in effetti l'attività del programmatore sia molto complessa e che il nostro cervello non è adeguato a sostenerne il compito. Quello che leggiamo dalle riflessioni di Dijkstra è che l'approccio alla programmazione è correlato al livello di qualità del software prodotto, e che i bravi programmatori sono coloro che hanno piena consapevolezza dei loro limiti mentali The competent programmer is fully aware of the strictly limited size of his own skull; therefore he approaches the programming task in full humility, and among other things he avoids clever tricks like the plague.

La scrittura di Test e la pratica del TDD è in effetti un approccio "umile" nella connottazione data da Dijkstra, fa parte dei metodi che permettono di realizzare programmi "intellettualmente gestibili". E' interessante che già nel 1972, Dijkstra, da questo punto di vista, ha avuto un intuizione che preccorreva i tempi The only effective way to raise the confidence level of a program significantly is to give a convincing proof of its correctness. But one should not first make the program and then prove its correctness, because then the requirement of providing the proof would only increase the poor programmer's burden. On the contrary: the programmer should let correctness proof and program grow hand in hand.

In conclusione We shall do a much better programming job, provided that we approach the task with a full appreciation of its tremendous difficulty, provided that we stick to modest and elegant programming languages, provided that we respect the intrinsic limitations of the human mind and approach the task as Very Humble Programmers.

TDD e Tipologie di Test Double

Sto seguendo con molta attenzione il corso di TDD con i screencast di thinkcode.tv realizzati a cura di Piergiuliano Bossi.

In particolare nell'ultima lezione sui Mock Objects viene proposta una tabella che illustra i vari tipi di Test Double, che come viene spiegato nel corso stesso, sono le tipologie di oggetti che vengono utilizzati per sostituire l'implementazione di dipendenze nella pratica del TDD (Test-Driven Development). Penso che questa tabella possa essere un utile riferimento per scegliere e riconoscere i tipi di classi Test Double implementati o da implementare, nonchè come riferimento per attribuirne un nome significativo.

Tabella Tipi di Test Double

Tipo

Descrizione

Dummy E' un oggetto privo di logica o dati significativi, utilizzato di solito solamente per soddisfare l'assegnamento di un valore non nullo a un parametro.
Fake Si tratta di un oggetto che reimplementa una logica altrimenti complessa in forma semplificata.
Stub Implementa logiche di tipo funzionale ritornando valori preimpostati.
Spy Registra i parametri con cui i suoi metodi sono stati invocati, esponendoli verso i test per un successivo esame.
Mock I Mock propriamente detti vengono programmati impostando delle aspettative su di essi, verificate al termine dell'esecuzone del test.

Ringrazio thinkcode.tv e Piergiuliano Bossi per l'autorizzazione a pubblicare la tabella.

System.DateTime.TryParseExact - Parse di data flessibile e indipendente dalla cultura del sistema

Recentemente ho avuto la necessità di intervenire per fare una fix su una procedura per il parsing di una data a partire da una stringa, in quanto con il metodo scelto, a seconda della cultura impostata sul sistema, la data veniva interpretata generando una pericolosa inversione del mese e del giorno, nel caso di conversione effettuata su un sistema con cultura inglese us.

Ho utilizzato quindi il metodo DateTime.TryParseExact che in questo caso si è rivelato potente è flessibile, in quanto da un lato la versione TryParse permette di evitare le eccezioni potendo verificare il risultato dell'operazione dal parametro di ritorno, inoltre permette di avere un controllo preciso sul formato potendo definire i pattern di riconoscimento della data, con un ulteriore livello di libertà dato dalle opzioni del parametro DateTimeStyles.

    public class Parser

    {

        public static DateTime? ParseDateStr(string str)

        {

            DateTime dtParsed;

 

            bool result = DateTime.TryParseExact(

                str,

                new[] { "dd/MM/yyyy", "dd-MM-yyyy" },

                null,

                DateTimeStyles.AllowWhiteSpaces,

                out dtParsed);

 

            if (result)

                return dtParsed;

 

            return null;

        }

    }

 

Con l'utilizzo mostrato sopra vengono riconosciute, in modo indipendente dalla cultura locale, date con questi formati

 

 "01/01/2010"

 "01-05-2010"

 "   31-12-2010"

 "01/01/2010   "

 " 31-12-2010 "

Windows.Forms, ToolStrip and Memory Leak

In questi giorni ho dovuto fare memory profiling di una grossa applicazione gestionale. Per farlo ho utilizzato un tool commerciale che ritengo molto buono. (Red Gate ANTS Memory Profiler).

Dopo un po' di tempo passato ad analizzare i dati quali Live Instance, Instance difference, Live size, Istance retention graph eccettera, ho scoperto nell'applicazione un memory leak causato da quelle form che utilizzavano il controllo Windows Form ToolStrip (vedi Classe ToolStrip su MSDN) conseguenza di un evento globale, in cui il controllo si "installa" per gestire il suo layout, come si può vedere da questo retention graph visualizzato da ANTS Memory Profiler. (Nota: Il grafico è realizzato su un form di esempio FormWithToolStrip creato per analizzare il problema)

 

Cercando su Internet ho trovato questo post di stackoverflow ToolStrip memory leak che ne spiega la ragione, indicando come soluzione quella di chiamare il metodo Dispose() sul controllo ToolStrip per permettere la deregistrazione di questi eventi.

 

Quello a cui bisogna stare molto attenti è quando il form, facendo parte di una applicazione MDI, viene aperto come modale chiamando il metodo Form.ShowDialog() (vedi Form.Close Method (Remarks) su MSDN) in questo caso il nostro form non viene finalizzato creando potenziali memory leak se oggetti ancora vivi o statici lo referenziano direttamente o indirettamente attraverso un controllo contenuto.

 

Perciò una soluzione è quella di includere il ciclo di vita del Form in un blocco using(...){}, in alternativa chiamare il metodo Dispose() (aggiungerei o Close()) alla fine dell'utilizzo del form modale.

Garbage Collector - Collect completa e deterministica ...

Ho avuto la necessità di scrivere un oggetto che gestisca in modo deterministico il ciclo di vita un oggetto aggregato, che deve essere finalizzato in modo opportuno.

Per raggiungere questo obiettivo ho utilizzato il pattern IDisposable (vedi Implement IDisposable Correctly), poi ho scritto un test per verificare che l'oggetto quando finalizzato attraverso IDisposable (cioè quando usato in un blocco using(...) { }), finalizzasse a sua volta correttamente l'oggetto aggregato.

Volevo però verificare anche che la stessa operazione di finilazzazione avvenisse quando l'oggetto è rilasciato dal Garbage Collector, così ho dovuto trovare un metodo per forzare in modo deterministico e sincrono la finalizzazione degli oggetti nel GC. Ho scritto quindi una semplice funzione che effettua questa operazione chiamando in modo opportuno metodi statici della classe GC.

    public static class UtilsGarbageCollector

    {

        public static void ForceCompleteCollectAndWait()

        {

            GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);

            GC.WaitForPendingFinalizers();

        }

    }

Ho scritto poi un test specifico per verificare le aspettive di questa funzione.

    [TestClass]

    public class TestFullSyncronizedCollect

    {

        [TestMethod]

        public void ShouldDeterministicCollectAllNotReferencedObject()

        {

            for (int i = 0; i < 100; i++)

            {

                new ClassA();

                new ClassB();

                new ClassC();

                new ClassD();

                new ClassE();

            }

 

            UtilsGarbageCollector.ForceCompleteCollectAndWait();

 

            Assert.AreEqual(0, BaseInstanceCounter.ClassInstance);

        }

    }

Dove le classi ClasseA...E sono sottoclassi di una particolare classe che conta le instanze "vive e non finalizzate".

    public class BaseInstanceCounter

    {

        public static int ClassInstance = 0;

 

        public BaseInstanceCounter()

        {

            ClassInstance++;

        }

 

        ~BaseInstanceCounter()

        {

            ClassInstance--;

        }

    }

 

    public class ClassA : BaseInstanceCounter { }

    public class ClassB : BaseInstanceCounter { }

    public class ClassC : BaseInstanceCounter { }

    public class ClassD : BaseInstanceCounter { }

    public class ClassE : BaseInstanceCounter { }

Se commentate nel test la chiamata al metodo UtilsGarbageCollector.ForceCompleteCollectAndWait(); questo non passa. (...è corretto dire quasi sempre non essendo deterministico il momento di finalizzazione degli oggetti da parte del GC)

Wildcard (* ?) to Regular Expression

Forse vi sarà capitato di dover inserire in qualche procedura una funzionalità di ricerca e/o filtro basata sui classici placeholder * (qualsiasi sequenza di carattere) e ? (qualsiasi carattere) usati come wildcard (cosidetti caratteri jolly).

Un po' di tempo fa ho scritto una funzione che utilizzando la classe RegEx (namespace System.Text.RegularExpressions) effettua il match su una stringa a partire da una wildcard costruita con i placeholder * e ?.

 

namespace TestWildCardMatch

{

    public class UtilsString

    {

        private const string WILDCARD_PLACEHOLDER = @"[\.:\\\w\s]"; //.:\[0-9a-zA-Z_][whitespace]

 

        public static bool IsMatchWithWildCard(string str, string wildCard, bool caseSensitive)

        {

            const string wildcharregexmulti = WILDCARD_PLACEHOLDER + "*";

            const string wildcharregexone = WILDCARD_PLACEHOLDER + "{1}";

            const string startmatch = @"^";

            const string endmatch = @"$";

            const string specialregexchars = @"[\^$.|+(){}"; //*? escluso

            const string escape = @"\";

 

            string strregex = caseSensitive ? wildCard : wildCard.ToLower();

 

            //Caratteri speciali escluso *? wildchar personalizzate

            foreach (char ch in specialregexchars)

                strregex = strregex.Replace(string.Empty + ch, escape + ch);

 

            //Traduzione wildchar *? in regexp

            strregex = strregex.Replace("?", wildcharregexone);

            strregex = strregex.Replace("*", wildcharregexmulti);

 

            return Regex.IsMatch(caseSensitive ? str : str.ToLower(), startmatch + strregex + endmatch);

        }

    }

}

Notare che il carattere jolly è definito attraverso la costante WILDCARD_PLACEHOLDER comprende i simboli indicati nel commento e può essere esteso secondo le esigenze di applicazione.

Di seguito un esempio di utilizzo:

using System;

using System.Collections.Generic;

 

namespace TestWildCardMatch

{

    public class UseWildCardMatch

    {

        public static void Test()

        {

            List<string> strings=new List<string>();

 

            strings.Add("Marco");

            strings.Add("Paolo");

            strings.Add("Mirko");

            strings.Add("Marta");

            strings.Add("Simone");

 

            List<string> matches = strings.FindAll(s => UtilsString.IsMatchWithWildCard(s, "??r*", false));

 

            foreach (string matched in matches)

                Console.WriteLine(matched);

        }

    }

}

Il risultato è il seguente: Marco, Mirko, Marta.