Month: May 2011

WP7 Mango : qualche link utile

5710.wp7_mango_fetta_thumb_29827258

Sono state annunciate da poco le novità della prossima versione “Mango” di Windows Phone 7 e la Microsoft ha rilasciato in versione Beta dell’SDK. Di seguito riporto alcuni link utili per iniziare ad apprezzare le nuove funzionalità.

Come installare Windows Phone Developer Tools 7.1 Beta (Mango)

Free Training Course su WP7 Mango

Alcuni video sui Mango Developer Tools :

Pre order libro “Professional Windows Embedded Compact” 7

E’ terminata la stesura del primo libro ufficiale della Wrox dedicato a Windows Embedded Compact 7 e scritto dagli MVPs Samuel Phung, David Jones e Thierry Joubert. La pubblicazione è prevista per Settembre 2011 ma è possibile già acquistarlo in pre order su Amazon.com al seguente link. Corro a farlo !!!

1882.Compact7_book_cover_5E5F5325

CLR – Thread Pool : elaborazioni periodiche con i timer

Capita spesso di dover eseguire periodicamente un’elaborazione ad intervalli prefissati. Una prima soluzione, assolutamente non efficace, potrebbe essere quella di predisporre un thread che al termine dell’elaborazione stessa va in sleep per un tempo pari al periodo richiesto, per poi rieseguire la computazione; tutto ciò in un opportuno ciclo. Considerando quanto detto una semplice idea e comunque un approccio non ottimale, la migliore soluzione la possiamo ottenere attraverso la classe Timer (del namespace System.Threading) che fa ampio uso del Thread Pool.

Considerando il costruttore più ampio di tale classe, esso prevede i seguenti parametri :

  • una delegate del tipo TimerCallback che rappresenta la callback da invocare ogni qual volta scatta il timer;
  • un oggetto con eventuali informazioni di stato da passare alla callback ogni qual volta viene invocata;
  • il dueTime, ossia il ritardo con il quale la callback viene invocata per la prima volta (specificando il valore 0, la callback viene invocata subito);
  • il periodo di invocazione della callback. Specificando Timeout.Infinite, viene disabilitata la periodicità e la callback sarà invocata solo una volta;

L’utilizzo di questa classe è strettamente legato al Thread Pool, in quanto il CLR utilizza uno dei worker thread del pool per invocare la callback associata al timer stesso. Ciò vuol dire che per più timer, impostati con periodi diversi e comunque aventi elaborazioni che non si sovrappongono nel tempo, il CLR è in grado di riciclare il medesimo thread per eseguire le corrispondenti callback con un notevole risparmio di risorse. E’ altresì ovvio che, qualora le elaborazioni delle callback si “sovrapponessero” nel tempo, il CLR sarebbe costretto ad allocare thread differenti per poterle eseguire.

Consideriamo la seguente semplice applicazione :

class Program
{

    // numero worker threads e completion port threads
    private static int workerThreads, completionPortThreads;

    private const int SCHEDULING_DELAY = 1000;    // delay avvio tra un thread e l'altro
    private const int NR_TIMER = 4;    // numero di thread
    private const long NR_DATA = 100000000;    // numero di elaborazioni

    private static Stopwatch sw;

    static void Main(string[] args)
    {
        Timer[] timers = new Timer[NR_TIMER];
        sw = new Stopwatch();

        WriteAvailableThreads();
        sw.Start();

        for (int idx = 0; idx < NR_TIMER; idx++)
        {
            timers[idx] = new Timer(TimerWork, idx, 5000, 1000);
            Thread.Sleep(SCHEDULING_DELAY);
        }
        Thread.Sleep(30000);

        for (int idx = 0; idx < NR_TIMER; idx++)
        {
            timers[idx].Dispose();
        }

        sw.Stop();
        Console.WriteLine("All works finished, elapsed time = {0} ms", sw.ElapsedMilliseconds);
        WriteAvailableThreads();

        Console.ReadLine();
    }

    private static void TimerWork(object state)
    {
        Console.WriteLine("Start TimerWork{0} at {1} ms with ThreadId {2}", state, sw.ElapsedMilliseconds, Thread.CurrentThread.ManagedThreadId);
        WriteAvailableThreads();

        long sum = 0;
        for (int i = 0; i < NR_DATA; i++)
        {
            sum += i;
        }

        Console.WriteLine("End TimerWork{0} at {1} ms with ThreadId {2}", state, sw.ElapsedMilliseconds, Thread.CurrentThread.ManagedThreadId);
    }

    private static void WriteAvailableThreads()
    {
        ThreadPool.GetAvailableThreads(out workerThreads, out completionPortThreads);
        Console.WriteLine("workerthreads = {0}, completionPortThreads = {1}", workerThreads, completionPortThreads);
    }
}

Attraverso la quale creiamo NR_TIMER timer con un delay l’uno rispetto l’altro pari a SCHEDULING_DELAY e supponiamo che la callback associata ai timer esegua un’elaborazione (una banalissima somma) con un certo numero di dati pari a NR_DATA. Assumiamo che il dueTime sia 5000 ms e che il timer scatti una sola volta e non periodicamente.

Di seguito un primo risultato.

2376.timer1_34E65D33

SCHEDULING_DELAY = 1000, NR_TIMER = 4, NR_DATA = 10000000

Si osserva che schedulando abbastanza lentamente l’avvio dei timer, allo scattare delle relative callback e considerando la velocità dell’elaborazione, il CLR riesce a riciclare sempre il medesimo worker thread.

Proviamo a velocizzare la schedulazione dei timer.

4401.timer2_60B6B122

SCHEDULING_DELAY = 10, NR_TIMER = 4, NR_DATA = 10000000

In questo caso, il CLR è costretto ad utilizzare due worker thread per eseguire le callback previste per i quattro timer.

Aggiungendo la periodicità al timer (in questo caso l’ho settata a 1000 ms), la situazione diventa non molto predicibile e complessa. Dallo screenshot che segue si evince come ad un certo punto il CLR abbia avuto la necessità di allocare 6 worker thread (da 1023 disponibili a 1017) per gestire 4 timer. Ciò è dovuto al fatto che, quando un worker thread è occupato e non ce ne sono altri già creati e disponibili, il CLR è costretto ad allocarne uno nuovo e che quelli già presenti nel pool vengono eliminati, se non usati, solo dopo un certo tempo di inattività.

3443.timer3_58BF0EC0

SCHEDULING_DELAY = 1000, NR_TIMER = 4, NR_DATA = 10000000, PERIOD = 1000

ASP.NET 4 : video corsi gratuiti dalla Pluralsight

Sul sito ufficiale di ASP.NET sono stati messi a disposizione una serie di corsi gratuti su ASP.NET 4 Web Forms ed ASP.NET MVC 3 da parte della Pluralsight, società di formazione partner della Microsoft.

Ovviamente tutti rigorosamente in lingua inglese ma penso che ne valga la pena seguirli. E’ necessario il plugin Silverlight per la loro visione.

Buona visione !

CLR – Thread Pool : attesa di eventi con i wait handlers

Capita spesso di dover eseguire una certa elaborazione al verificarsi di un evento. Ovviamente, non faccio riferimento agli eventi scatenati sulla UI (es. click di un bottone,..) ai quali la gestione è demandata ai relativi event handlers, ma mi riferisco ad eventi intrinseci dell’applicazione che magari servono per sincronizzare uno o più thread fra loro (es. sbloccare un thread nel momento in cui un altro thread ha terminato una computazione ed ha reso disponibili dei dati).

La soluzione tipica è quella di creare un WaitHandle (concretamente un AutoResetEvent oManualResetEvent in base alle esigenze) ed un thread, dopodichè bloccare quest’ultimo mediante una WaitOne() sull’evento stesso. In questo modo, il thread va in wait e si sbloccherà nel momento in cui “qualcuno” (un altro thread o il main thread) eseguirà unaSet() sull’evento. Questa tecnica, prevede comunque la creazione di un thread custom da parte dello sviluppatore.

Ebbene, anche in una situazione di questo tipo, ci viene fornito supporto dal thread pool del CLR. Infatti, utilizzando il metodo statico RegisterWaitForSingleObject() della classeThreadPool, è possibile demandare al CLR il monitoring di un evento e far eseguire una callback nel caso in cui quest’ultimo diventi signaled o nel caso in cui scatti un timeout.

Il metodo RegisterWaitForSingleObject prevede i seguenti parametri :

  • un WaitHandle, ossia l’evento da monitorare;
  • la callback da eseguire nel momento in cui l’evento viene segnalato o scatta il timeout. La callaback è definita attraverso il delegate WaitOrTimerCallback;
  • un oggetto di stato da passare alla callback (utile per trasferire dati a quest’ultima);
  • un timeout in millisecondi allo scattare del quale la callback viene comunque invocata nonostante l’evento non sia stato segnalato;
  • una flag che indica se il monitoring dell’evento deve essere ripetuto nel tempo oppure se va eseguito una sola volta (nel cui caso al signaling dell’evento o alla scattare del timeout, l’evento non verrà più monitorato);

Infine, esso ritorna un RegisteredWaitHandle sul quale va eseguito il metodo diUnregister() nel momento in cui non è più necessario, in modo da liberare le risorse allocate per l’evento. E’ da osservare che il delegate WaitOrTimerCallback prevede un primo parametro che rappresenta lo stato eventualmente passato alla callback ed un secondo parametro booleano che indica se la callback è stata chiamata per timeout o meno.

Consideriamo la seguente applicazione console :

class Program
{
    // numero worker threads e completion port threads
    private static int workerThreads, completionPortThreads;

    private const int SCHEDULING_DELAY = 1000;
    private const int NR_WAIT_CALLBACK = 2;
    private const long NR_DATA = 100000000;
    private const int WAIT_TIMEOUT = 5000;
    private const int SET_DELAY = 1000;

    private static AutoResetEvent[] waitEvent;              // wait handles da monitorare
    private static AutoResetEvent[] syncEvent;              // wait handles per la sincronizzazione con il main thread
    private static RegisteredWaitHandle[] regWaitHandle;    // handle relativi alle risorse allocate dal manager
                                                            // del thread pool per il monitoring
    private static Stopwatch sw;

    static void Main(string[] args)
    {
        waitEvent = new AutoResetEvent[NR_WAIT_CALLBACK];
        syncEvent = new AutoResetEvent[NR_WAIT_CALLBACK];
        regWaitHandle = new RegisteredWaitHandle[NR_WAIT_CALLBACK];
        sw = new Stopwatch();

        WriteAvailableThreads();
        sw.Start();

        Console.WriteLine("Generating wait event...");
        for (int idx = 0; idx < NR_WAIT_CALLBACK; idx++)
        {
            waitEvent[idx] = new AutoResetEvent(false);
            syncEvent[idx] = new AutoResetEvent(false);
            regWaitHandle[idx] = ThreadPool.RegisterWaitForSingleObject(waitEvent[idx], WaitMethod, idx, WAIT_TIMEOUT, true);
            Thread.Sleep(SCHEDULING_DELAY);
        }

        Console.WriteLine("Waiting for all wait methods...");
        WaitHandle.WaitAll(syncEvent);

        sw.Stop();
        Console.WriteLine("All  wait methods finished, elapsed time = {0} ms", sw.ElapsedMilliseconds);

        for (int j = 0; j < NR_WAIT_CALLBACK; j++)
        {
            regWaitHandle[j].Unregister(waitEvent[j]);
        }

        Console.ReadLine();
    }

    private static void WriteAvailableThreads()
    {
        ThreadPool.GetAvailableThreads(out workerThreads, out completionPortThreads);
        Console.WriteLine("workerthreads = {0}, completionPortThreads = {1}", workerThreads, completionPortThreads);
    }

    private static void WaitMethod(object state, bool timeout)
    {
        int waitMethodIdx = (int)state;
        Console.WriteLine("Start WaitMethod{0} with timeout={1} at {2} ms with ThreadId {3}", waitMethodIdx + 1, timeout, sw.ElapsedMilliseconds, Thread.CurrentThread.ManagedThreadId);
        WriteAvailableThreads();

        int sum = 0;
        for (int i = 0; i < NR_DATA; i++)
        {
            sum += i;
        }
        Console.WriteLine("End WaitMethod{0} at {1} ms with ThreadId {2}", waitMethodIdx + 1, sw.ElapsedMilliseconds, Thread.CurrentThread.ManagedThreadId);
        syncEvent[waitMethodIdx].Set();
    }
}

In cui :

  • waitEvent è l’array con i wait handle da monitorare e sui quali saremo in attesa;
  • syncEvent  è l’array con i wait handle per la sincronizzazione tra il main thread ed i thread relativi alle callback eseguite in seguito allo scattare degli eventi o del timeout;
  • regWaitHandle è l’array con i RegisteredWaitHandle usati dal manager del thread pool per gestire il monitoring;

L’applicativo non fa nient’altro che registrare NR_WAIT_CALLBACK in attesa sugli eventi dell’array waitEvent con un tempo di schedulazione di SCHEDULING_DELAY millisecondi. Inoltre, è definito un timeout pari a WAIT_TIMEOUT millisecondi. La prima versione del codice applicativo, prevede che le callback si sblocchino tutte per timeout. Infine, NR_DATA servirà per simulare un’elaborazione nelle callback.

Proviamo ad eseguirla con i seguenti valori :

  • SCHEDULING_DELAY = 100;
  • NR_WAIT_CALLBACK = 2;
  • NR_DATA = 100000000;
  • WAIT_TIMEOUT = 5000;

Otteniamo il seguente output :

1805.wait_handle_1_1B282EDF

SCHEDULING_DELAY = 100, NR_WAIT_CALLBACK = 2, WAIT_TIMEOUT = 5000

In questo caso, la registrazione di wait sull’handle avviene rapidamente (100 ms di delay) e si osserva che, allo scattare del timeout (5 sec) ovviamente le due callback dovranno essere chiamate “quasi” in contemporanea, considerando anche il tempo abbastanza lungo di elaborazione per ciascuna di esse. Per questo motivo, vengono allocati due completion port thread per eseguire tali callback.

Proviamo ad aumentare il delay tra una richiesta di registrazione e l’altra. Otteniamo …

8741.wait_handle_2_78F02358

SCHEDULING_DELAY = 1000, NR_WAIT_CALLBACK = 2, WAIT_TIMEOUT = 5000

In questo caso, la prima callback verrà invocata un secondo prima della seconda callback e considerato il fatto che l’elaborazione termina entro tale secondo, il manager del thread pool riesce a riciclare il medesimo completion port thread per gestirle entrambe. Se proviamo, però, ad aumentare il tempo di elaborazione (portando NR_DATA a 1000000000), la prima callback non sarà ancora terminata quando scatterà il timeout per la seconda, per cui il manager del thread pool sarà costretto ad allocare un nuovo thread.

8154.wait_handle_3_5F1BED29

SCHEDULING_DELAY = 1000, NR_WAIT_CALLBACK = 2, WAIT_TIMEOUT = 5000, NR_DATA = 1000000000

Aggiungiamo ora la condizione di sblocco settando gli eventi. Per fare questo, inseriamo le seguenti righe di codice subito dopo il ciclo di registrazione delle callback :

for (int k = 0; k < NR_WAIT_CALLBACK; k++)
{
    waitEvent[k].Set();
    Thread.Sleep(SET_DELAY);
}

Tra un’operazione di signaling di un evento e la successiva, lasciamo trascorrere un certo tempo per simulare uno sblocco differito. Ovviamente, modulando questo tempo, verrà riciclato o meno lo stesso thread del pool per eseguire la callback :

  • con SET_DELAY piccolo, i signaling sono così ravvicinati e magari l’elaborazione nella callback abbastanza lunga da costringere il CLR ad allocare thread separati nel pool;
  • con SET_DELAY grande, considerando il medesimo tempo di elaborazione nella callback, passa comunque più tempo tra un signaling ed il successivo per cui il CLR ha il tempo di riciclare il medesimo thread;

Riporto di seguito due possibili situazioni

1513.wait_handle_4_733509B2

SET_DELAY=100

0825.wait_handle_5_446F5110

SET_DELAY=1000

CLR – Thread Pool : uso dei worker thread

Una delle possibilità di utilizzo del thread pool è quella di usufruire dei thread al suo interno per poter eseguire in parallelo delle operazione CPU bound, ossia di calcolo puro. Ovviamente, il CLR definisce una dimensione massima del pool, sia in termini di worker thread che di completion port thread. Per quanto concerne i worker thread, i limiti di default sono i seguenti :

  • 1023 nel .Net Framework 4.0 (ambiente a 32 bit);
  • 32768 nel .Net Framework 4.0 (ambiente a 64 bit);
  • 250 per core nel .Net Framework 3.5;
  • 25 per core nel .Net Framework 2.0;

Tali valori possono essere modificati al ribasso, utilizzando il metodo staticoThreadPool.SetMaxThreads, così come è possibile definirne il numero minimo medianteThreadPool.SetMinThreads. Invece, il numero di thread disponibili nel pool in un dato momento è restituito da ThreadPool.GetAvailableThreads.

Partiamo con una semplice analisi, considerando il seguente applicativo console :

<br />class Program<br />{<br /><%%KEEPWHITESPACE%%>    // numero worker threads e completion port threads<br /><%%KEEPWHITESPACE%%>    private static int workerThreads, completionPortThreads;<br /><br /><%%KEEPWHITESPACE%%>    private const int SCHEDULING_DELAY = 1000;    // delay avvio tra un thread e l'altro<br /><%%KEEPWHITESPACE%%>    private const int NR_WORKER_THREADS = 2;    // numero di thread<br /><%%KEEPWHITESPACE%%>    private const long NR_DATA = 100000000;    // numero di elaborazioni<br /><br /><%%KEEPWHITESPACE%%>    private static int[] inputData;<br /><%%KEEPWHITESPACE%%>    private static int[] outputData;<br /><%%KEEPWHITESPACE%%>    private static ManualResetEvent[] resetEvent;<br /><%%KEEPWHITESPACE%%>    private static Stopwatch sw;<br /><br /><%%KEEPWHITESPACE%%>    static void Main(string[] args)<br /><%%KEEPWHITESPACE%%>    {<br /><%%KEEPWHITESPACE%%>        inputData = new int[NR_DATA];<br /><%%KEEPWHITESPACE%%>        outputData = new int[NR_DATA];<br /><%%KEEPWHITESPACE%%>        resetEvent = new ManualResetEvent[NR_WORKER_THREADS];<br /><%%KEEPWHITESPACE%%>        sw = new Stopwatch();<br /><br /><%%KEEPWHITESPACE%%>        Console.WriteLine("Generating random data...");<br /><%%KEEPWHITESPACE%%>        Random rand = new Random();<br /><%%KEEPWHITESPACE%%>        for (int idx = 0; idx &lt; NR_DATA; idx++)<br /><%%KEEPWHITESPACE%%>        {<br /><%%KEEPWHITESPACE%%>            inputData[idx] = rand.Next(10);<br /><%%KEEPWHITESPACE%%>        }<br /><br /><%%KEEPWHITESPACE%%>        WriteAvailableThreads();<br /><%%KEEPWHITESPACE%%>        sw.Start();<br /><br /><%%KEEPWHITESPACE%%>        Console.WriteLine("Start scheduling works...");<br /><%%KEEPWHITESPACE%%>        for (int j = 0; j &lt; NR_WORKER_THREADS; j++)<br /><%%KEEPWHITESPACE%%>        {<br /><%%KEEPWHITESPACE%%>            resetEvent[j] = new ManualResetEvent(false);<br /><%%KEEPWHITESPACE%%>            ThreadPool.QueueUserWorkItem(DoWork, j);<br /><%%KEEPWHITESPACE%%>            Thread.Sleep(SCHEDULING_DELAY);<br /><%%KEEPWHITESPACE%%>        }<br /><%%KEEPWHITESPACE%%>        Console.WriteLine("Waiting for all works...");<br /><%%KEEPWHITESPACE%%>        WaitHandle.WaitAll(resetEvent);<br /><%%KEEPWHITESPACE%%>        sw.Stop();<br /><%%KEEPWHITESPACE%%>        Console.WriteLine("All works finished, elapsed time = {0} ms", sw.ElapsedMilliseconds);<br /><br /><%%KEEPWHITESPACE%%>        Console.ReadLine();<br /><%%KEEPWHITESPACE%%>    }<br /><br /><%%KEEPWHITESPACE%%>    private static void DoWork(object state)<br /><%%KEEPWHITESPACE%%>    {<br /><%%KEEPWHITESPACE%%>        int workIdx = (int)state;<br /><%%KEEPWHITESPACE%%>        Console.WriteLine("Start DoWork{0} at {1} ms with ThreadId {2}", workIdx + 1, sw.ElapsedMilliseconds, Thread.CurrentThread.ManagedThreadId);<br /><%%KEEPWHITESPACE%%>        WriteAvailableThreads();<br /><br /><%%KEEPWHITESPACE%%>        for (int idx = 0; idx &lt; NR_DATA; idx++)<br /><%%KEEPWHITESPACE%%>        {<br /><%%KEEPWHITESPACE%%>            outputData[workIdx] += inputData[idx] * inputData[idx];<br /><%%KEEPWHITESPACE%%>        }<br /><br /><%%KEEPWHITESPACE%%>        Console.WriteLine("End DoWork{0} at {1} ms with ThreadId {2}", workIdx + 1, sw.ElapsedMilliseconds, Thread.CurrentThread.ManagedThreadId);<br /><%%KEEPWHITESPACE%%>        resetEvent[workIdx].Set();<br /><%%KEEPWHITESPACE%%>    }<br /><br /><%%KEEPWHITESPACE%%>    private static void WriteAvailableThreads()<br /><%%KEEPWHITESPACE%%>    {<br /><%%KEEPWHITESPACE%%>        ThreadPool.GetAvailableThreads(out workerThreads, out completionPortThreads);<br /><%%KEEPWHITESPACE%%>        Console.WriteLine("workerthreads = {0}, completionPortThreads = {1}", workerThreads, completionPortThreads);<br /><%%KEEPWHITESPACE%%>    }<br />}<br />

di cui abbiamo :

  • un metodo DoWork che simula un’elaborazione CPU bound, eseguendo una semplice computazione sugli elementi dell’array inputData, la cui dimensione è fissata mediante la costante NR_DATA;
  • il Main che ha il compito di lanciare/schedulare un certo numero di elaborazioni parallele eseguite con il medesimo metodo DoWork; il numero è fissato mediante la costante NR_WORKER_THREADS e il delay tra un avvio e l’altro mediante laSCHEDULING_DELAY;
  • l’array resetEvent di ManualResetEvent che serve per la sincronizzazione tra il thread principale e i thread che via via terminano;

Per quanto riguarda l’output, l’applicativo visualizza sulla console i tempi di avvio dei thread, il loro ID (in modo da poter distinguere l’allocazione di un nuovo thread dal pool rispetto ad un altro) ed il numero di worker thread disponibili nel pool.

Modulando i valori delle tre costanti NR_DATA (dimensione array da computare),NR_WORKER_THREADS (numero di elaborazioni parallele) e SCHEDULING_DELAY (delay tra una richiesta di elaborazione e la successiva) è possibile descrivere la variazione del comportamento del CLR nell’utilizzo del thread pool. E’ da precisare che i test, saranno eseguiti su una CPU dual core.

Partiamo con i seguenti valori ed osserviamone il risultato :

  • SCHEDULING_DELAY = 10;
  • NR_WORKER_THREADS = 2;
  • NR_DATA = 100000000;

7077.worker_thread_1_7CA54CFE

SCHEDULING_DELAY = 10; NR_WORKER_THREADS = 2; NR_DATA = 100000000;

Si osserva, sulla base delle due elaborazioni parallele richieste, che vengono allocati due thread separati nel pool (i due ID sono differenti ed il numero di worker thread disponibili è decrementato di due rispetto la condizione iniziale) e che vengono eseguiti parallelamente dai due core della CPU (il tempo di esecuzione è pressoché lo stesso).

Riduciamo drasticamente il tempo necessario all’elaborazione portando il valore NR_DATA a 1000.

1004.worker_thread_2_618C7DF0

SCHEDULING_DELAY = 10; NR_WORKER_THREADS = 2; NR_DATA = 1000;

In questo caso, osserviamo che l’elaborazione è così rapida che il CLR tende ad eseguire le due richieste in maniera sequenziale riutilizzando il medesimo thread (stesso ID). In questo caso, sarà stato utilizzato un solo core della CPU. Ovviamente, una situazione simile si ottiene aumentando il delay tra un avvio e l’altro considerando un tempo di elaborazione lungo, in quanto si dà al CLR il tempo di riciclare lo stesso thread nel pool.

4555.worker_thread_3_47B847C1

SCHEDULING_DELAY = 1000; NR_WORKER_THREADS = 2; NR_DATA = 100000000;

Proviamo ad aumentare il numero di elaborazioni parallele portandole a 4. E’ ovvio che, avendo a disposizione una CPU dual core, sarà possibile parallelizzare realmente solo 2 computazioni ed eseguire più context switch per fornire il multithreading per le altre elaborazioni. Ovviamente il CLR tenderà ad utilizzare più pool nel thread, se assumiamo un tempo di elaborazione piuttosto lungo. Un possibile output sarà il seguente.

2526.worker_thread_4_78D78261

SCHEDULING_DELAY = 10; NR_WORKER_THREADS = 4; NR_DATA = 100000000;

In questo caso, due elaborazioni vengono avviate quasi contemporaneamente e per la terza viene allocato un nuovo thread. Al termine della prima elaborazione, viene avviata la quarta riciclando il thread di quella appena terminata (ID = 11).

La mia tesi…un ritorno al mio passato in Java

Oggi navigando in rete mi sono imbattuto in un link interessante….la mia tesi, discussa il 21 Febbraio del 2006, in formato PDF sul sito del CNR nella sezione relativa al mio relatore, il Prof. Ing. Antonio D’Acierno. Mi ricordo ancora la frase pronunciata dal professore alla commissione quando entrai con una delle copie rilegate di “sole” circa 450 pagine…”..ti avevo detto di non rilegare tutte e tre le copie insieme…” ridendo; insomma forse avevo un pò esagerato ! Ma quando c’è passione … i contenuti che si vogliono esprimere non bastano mai !

Occhiolino

Un ritorno al passato, quando utilizzavo il Java per poi passare definitivamente alla “concorrenza” !

Così per un eventuale curiosità posto qui il link :

Frameworks MVC per lo sviluppo di WEB Applications: Java Server Faces e Struts

CLR – Thread Pool : Introduzione

Generalmente, la creazione di un thread è molto costosa in termini di risorse da allocare ed in termini di tempo. Inoltre, avere molti thread attivi nel sistema per poter eseguire molteplici operazioni in parallelo tende a ridurre le performance del sistema stesso, in quando il processore è costretto ad eseguire numerosi context switch durante lo scheduling.

Per ottimizzare l’utilizzo delle risorse e migliorare le performance, ci viene in aiuto il thread pool fornito dal CLR. Bisogna precisare, però, che la funzionalità del thread pool è nativa in Windows e che tutte le classi ed i metodi forniti dal CLR ne rappresentano comunque un wrapper.

1727.image_5B05635A

Attraverso il thread pool, il CLR fornisce appunto un pool di thread che possiamo utilizzare per eseguire elaborazioni in parallelo. Il manager del thread pool ha a disposizione una coda all’interno della quale pervengono tutte le richieste di elaborazione parallela fatte dalla nostra applicazione. Ogni qual volta viene inserita una nuova richiesta, il manager analizza se nel thread pool è disponibile un thread libero in stato di wait e pronto per poter essere assegnato all’esecuzione di una nuova elaborazione. Nel caso affermativo, viene eseguita tale assegnazione e la computazione da noi richiesta viene eseguita all’interno di un thread che deve essere solo avviato e non creato (in quanto già esistente) con un notevole risparmio di tempo (soprattutto in fase di avvio) e di risorse (non dobbiamo allocare il contesto di un nuovo thread). Viceversa, nel caso in cui, un thread non fosse disponibile nel pool, esso viene creato (ovviamente con una riduzione di performance e maggior utilizzo di risorse) ed assegnato all’elaborazione da eseguire. Nel momento in cui, però, un thread del pool termina la sua elaborazione esso non viene eliminato ma rimesso nel pool in stato di wait e pronto per essere assegnato ad altre richieste. L’eliminazione del thread, per risparmiare risorse allocate, viene eseguita solo nel caso in cui un thread non viene utilizzato per un certo tempo definito nel CLR.

Da questo scenario si evince che i vantaggi possono essere :

  • riduzione al minimo dell’allocazione di risorse;
  • velocità nell’avvio di un’elaborazione parallela;
  • miglioramento generale delle performance;

I parametri che possono, però, influenzare l’utilizzo ottimale del pool sono molteplici ed in particolare :

  • frequenza di schedulazione : intesa come la frequenza con cui la nostra applicazione richiede l’avvio di una computazione utilizzando il thread pool. E’ ovvio che se tale frequenza è elevata, ci saranno maggiori possibilità che il manager del pool debba allocare più thread dal pool stesso considerando che la coda delle richieste tende a popolarsi rapidamente. Con una frequenza di schedulazione bassa, probabilmente avremo più possibilità di riutilizzare lo stesso thread per eseguire tutte le richieste pervenute nella coda. E’ anche vero che ciò è legato alla durata della computazione (vedi dopo);
  • durata della computazione : indica la durata dell’elaborazione che vogliamo far eseguire ad un thread del pool. Più essa è alta e più un thread rimane occupato  e quindi il manager del pool dovrà allocare e utilizzare altri thread per esaudire ulteriori richieste nella coda;
  • numero di elaborazioni parallele : inteso come il numero di richieste che la nostra applicazione fa al manager del pool per eseguire elaborazioni parallele. Maggiore è il numero di richieste e maggiore è la probabilità che verranno utilizzati più thread nel pool per esaudirle;
  • numero di core del processore : questo aspetto hardware influisce soprattutto sulle performance, considerando che pur nel caso di più thread allocati dal pool, essi saranno distribuiti su più core e quindi la velocità di esecuzione sarà notevole;

Dalle considerazioni suddette, si evince che, per ottimizzare l’utilizzo di risorse del pool è necessario eseguire delle elaborazioni molto brevi, in modo che il manager sia in grado di riciclare sempre lo stesso thread per eseguirle. E’ anche vero che in questo caso le nostre operazioni verranno “sequenzializzate”. Un miglioramento delle performance a discapito di maggiori risorse allocate, lo otteniamo soprattutto nel caso in cui le computazioni sono più lunghe, per cui il manager è costretto ad allocare più thread, e magari usufruire di un processore multicore in modo da parallelizzare “realmente” le esecuzioni.

E’ ovvio che in questi termini, possono essere fatti numerosi ragionamenti alterando in un modo o nell’altro i parametri suddetti che vanno ad influenzare l’utilizzo ottimale del pool e delle risorse di sistema.

E’ inoltre importante sottolineare che, le computazioni per le quali richiediamo un’elaborazione parallela non devono prevedere dei meccanismi di sincronizzazione o accesso a risorse condivise, altrimenti decade il miglioramento di performance dovuto al parallelismo, soprattutto in un sistema multicore.

Tornando all’implementazione del pool nel CLR, esso fornisce due tipologie di thread :

  • worker threads : utilizzati per operazioni di tipo CPU bound;
  • completion port threads : thread che vengono avviati tipicamente al termine di operazioni asincrone su file o socket. L’uso di tali thread si basa proprio sul meccanismo delle completion port fornito da Windows. Infatti, ogni qual volta viene avviata un’operazione asincrona, uno dei meccanismi forniti da Windows per segnalare il termine di tale operazione, è quello di inserire un pacchetto in una coda specifica opportunamente monitorata da un thread, che nel nostro caso sarà proprio un completion port thread nel quale sarà eseguita la callback prevista al termine dell’operazione asincrona;

Prima di terminare questa breve introduzione per poi “sporcarci” le mani negli articoli successivi, anticipo quelli che saranno i possibili campi di utilizzo del thread pool :

  • worker thread : possibilità di utilizzare un thread per eseguire un’operazione CPU bound;
  • wait event : utilizzare un thread del pool per monitorare lo stato di un evento e di far eseguire una nostra callback nel momento in cui l’evento risulta segnalato oppure un eventuale timeout è scattato;
  • timer : uso di un thread per eseguire operazioni periodiche basate appunto su un timer;
  • I/O asincrono : possibilità di lanciare operazioni asincrone su file o socket per poi demandare ad un thread del pool il compito di eseguire una callback al termine di tale operazione;

Windows Embedded CE 6.0 R3 QFEs : Aprile 2011

Sono stati rilasciati i QFEs di Aprile 2011 per Windows Embedded CE 6.0 R3 e sono scaricabili al seguente indirizzo :

Windows Embedded CE 6.0 Monthly Update April 2011

Riporto di seguito i principali fix, come descritto nel file allegato al download :

Component: Internet Explorer

  • 110405_KB2481555 – This update addresses some Internet Explorer issues.

Component: Kernel

  • 110428_KB2529142 – User mode drivers loaded via LoadDriver are not loaded as non-pageable.
  • 110426_KB2520539 – This update addresses a race condition between UnmapViewOfFile and MapViewOfFile for the named-file mapping object.
  • 110404_KB2521622 – This update addresses an issue in kernel VM implementation specific to ARM processor.

Component: LBCTL

  • 110428_KB2533422 – This update addresses and issue with JScript onClick event not working properly.

Component: Script

  • 110428_KB2527087 – VBScript may not handle InputBox and MsgBox correctly.

Component: UDFS/CDFS

  • 110420_KB2531374 – CD may not play if some file names have Unicode characters.