System.Threading.Tasks.Task implementa IAsyncResult(.NET 4.0 Beta 2)

by Marco 13. gennaio 2010 14.18

L’ IAsyncResult design pattern è un pattern per l’esecuzione asincrona di operazioni che viene largamente usato all’interno del framework in ogni sua parte; consiste nella scrittura di due metodi che permettono di iniziare un’operazione in modo asincrono e di un metodo che la completa, un esempio di una classe che implementa questo pattern potrebbe essere:

    public class AsyncLib
   {
       public int Foo(int parameter)
       {
           ...operazione
       }

       public IAsyncResult BeginFoo(int parameter, AsyncCallback callback, object state)
       {
          ...chiamata a Foo(int parameter) in thread separato e ritorno di un oggeto
          che implementa IAsyncResult
       }

       public int EndFoo(IAsyncResult asyncResult)
       {
           …se l’operazione è finita torno il risultato altrimenti blocco il thread che ha chiamato
       }
   }

il funzionamento è abbastanza semplice: chiamo BeginOperazione che ritorna immediatamente un’istanza di una classe che implementa l’interfaccia IAsyncResult che possiamo interrogare per sapere a che punto è la nostra operazione, o possiamo passare un delegate di tipo AsyncCallBack al metodo stesso per farci chiamare una volta che l’operazione è finita. Quando l’operazione asincrona è terminata(lo sappiamo grazie all’oggetto IAsyncResult o perchè ci viene chiamata la callBack) dobbiamo chiamare il metodo EndOperazione e passare l’istanza ritornata dal metodo BeginOperazione, a quel punto abbiamo in mano il risultato e possiamo farci quello che vogliamo. Questa spiegazione non è completa, ma non è lo scopo del post vi lascio il link alla documentazione ufficiale Asynchronous Programming Design Patterns. Lo stesso pattern viene implementato automaticamente se utilizziamo un delegate, ovvero viene compilata per noi una classe con i metodi Begin/End.

Se dobbiamo far implementare questo pattern alle nostre librerie la cosa più complessa da fare è quella di creare una classe che implementi IAsyncResult nel modo corretto e sincronizzare il tutto con il lavoro asincrono. Le cose da prendere in esame non sono poi così banali, dobbiamo accodare il lavoro da “qualche parte”(dipende da quale tipo di thread vogliamo utilizzare, thread pool, un nostro thread etc…),implementare ciò che l’interfaccia ci richiede(le cui cose più complesse sono come gestire l’oggetto di sincronizzazione e alcuni stati richiesti dall’oggetto in modo perfomante e corretto) e chiamare la callback in caso venga fornita alla fine dell’operazione asincrona. Problema ancora più subdolo è il fatto che la nostra libreria potrebbe essere chiamata in contesti di threading diversi, quindi in caso di callback dobbiamo assicurarci che tutto rispetti le regole di questo contesto. L’implementazione costa un pò di righe di codice che non sono così banali a chiunque.

Il team delle Task Parallel Library ha fortunatamente pensato anche a questo e ha gentilmente fatto implementare alla classe Task l’interfaccia IAsyncResult. Questo fa si che scrivere con .NET 4.0 un custom IAsyncResult diventi un gioco da ragazzi, di seguito un esempio:

  public class AsyncLib
   {
       public int Foo(int millisecond)
       {
           //simulo un carico di lavoro
           Thread.Sleep(millisecond);
           //simulo un risultato
           return new Random().Next(int.MaxValue);
       }

       public IAsyncResult BeginFoo(int i, AsyncCallback callback,
           object state)
       {
          

          Task<int> task = new Task<int>(o => Foo(i), state);

           task.ContinueWith(t =>
           {
               if (callback != null)
                   callback(t);
           },
           SynchronizationContext.Current == null ?
           TaskScheduler.Default :
           TaskScheduler.FromCurrentSynchronizationContext()
);

           task.Start();

           return task;

         }

       public int EndFoo(IAsyncResult asyncResult)
       {
           Task<int> result = asyncResult as Task<int>;

           if (result == null)
               throw new ArgumentException("Bad async result.");

           return result.Result;
       }
   }

cosa ne dite?Semplicemente la nostra Begin ritorna un task che esegue prima la chiamata a Foo in modo asincrono e poi attraverso ContinueWith esegue la callback(in caso venga passata) nel constesto di threading adeguato
(TaskScheduler.FromCurrentSynchronizationContext() o TaskScheduler.Default). Se contiamo le righe di codice sono 4 per la Begin(sono dovuto andare a capo per farci stare tutto)e 3/4 per la End. Con così poco semplice codice utilizzare questo pattern sarà alla portata di tutti, in quanto l’unica cosa non chiarissima è forse il check del contesto per scegliere il TaskScheduler adeguato, ma siamo in Beta 2 magari qualcosa di meglio lo pensano, per il resto non si vede l’ombra di XXXResetEvent, lock, volatile, ThreadPool, Thread,WaitXXX etc….sono solo 2 Task uno “attaccato” alla fine dell’altro…semplice no?

…ah dimenticavo, chiaramente funziona ovunque, Console app, Windows Form app, WPF app etc…

Se volete una demo contattatemi

Vota questo post per primo

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: , , ,

.Net | Concurrent Programming | Multithreading

TaskScheduler.FromCurrentSynchronizationContext() (.NET 4.0 Beta 2)

by Marco 7. gennaio 2010 20.39

Tutti coloro che hanno sviluppato interfaccie che gestiscono una grande quantità di dati o che eseguono lunghe operazioni, si sono dovuti scontrare prima o poi con il problema del “blocco” dell’interfaccia dovuto al fatto che la “pesantezza” dell’operazione veniva eseguita nel famoso “thread di UI” cioè quel thread che ha il compito di gestire la coda dei messaggi associata ai controlli che lui stesso ha creato. L’indisponibilità di questo thread(speso a fare operazioni “lunghe”) porta l’interfaccia a bloccarsi in quanto i messaggi nella coda non vengono gestiti e quindi la nostra interfaccia non si può ridisegnare correttamente o reagire all’attività dei nostri utenti.

Per ovviare a questo problema l’unica soluzione è quella di effettuare l’operazione “lunga” in un thread separato, così da liberare il “thread di UI” che può senza problemi gestire la sua bella coda di messaggi. L’unico problema di questo approccio è il fatto che nel momento che dobbiamo riflettere il risultato dell’ operazione “lunga” sull’interfaccia non lo possiamo fare da un thread che non sia quello di UI. L’unico modo di risolvere questo problema è quello di fare il “marshal” sul “thread di UI” ovvero ci serve un meccanismo per passare i dati al thread di UI e far fare a lui la modifica dei nostri controlli. I 2 principali framework grafici che “soffrono” di questo problema sono chiaramente Windows Form e WPF. Su internet si trovano tonnellate di articoli che spiegano come fare il “marshal” nelle varie piattaforme, l’operazione è abbastanza semplice, l’unico difetto è che purtroppo le modalità sono differenti da un framework ad un’altro(esempio l’interfaccia ISyncronizeInvoke per Windows Form e la classe Dispatcher per WPF) e questo è un bel problema per chi deve ad esempio scrivere una libreria che deve essere asincrona e utilizzabile ALLO STESSO MODO su qualsiasi piattaforma grafica venga usata(diciamo che più che di piattaforma grafica possiamo parlare di contesto di esecuzione).

Una delle possibilità che abbiamo per generalizzare la possibilità di gestire il marshal è quella di utilizzare la classe SyncronizationContext che attraverso la proprietà statica Current ci mette a disposizione un paio di metodi(Post se vogliamo un’esecuzione asincrona o Send la vogliamo sincrona) per eseguire un delegate nel giusto Context(diciamo il thread di UI se stiamo usando WPF o WindowsForm, ma context è un concetto generico).

Ad esempio se vogliamo modificare il content di un bottone in WPF un’ipotetico handler dell’evento click potrebbe essere:

private void button1_Click(object sender, RoutedEventArgs e)
{          
  SynchronizationContext context = SynchronizationContext.Current;
   ThreadPool.QueueUserWorkItem(a =>
           {
              System.Threading.Thread.Sleep(5000);//Simulo il carico di lavoro
             context.Post(o=>this.button1.Content=o,"Nuova Content del bottone"); //modifico una proprietà di interfaccia
           });
  }

in questo esempio prima salvo il Current context attraverso la proprietà SyncronizationContext.Current e poi in un thread del thread pool di .NET(quindi non un thread di UI) faccio il “marshal” asincrono usando il metodo Post sul context salvato(il context nel thread di thread pool non è valido). Il fatto che sto utilizzando WPF significa che qualcuno(il runtime di WPF) ha “attaccato” come Current context una implementazione personalizzata(la classe è la DispatcherSynchronizationContext nel namespace System.Windows.Threading) della classe SynchronizationContext che al suo interno usa la classe Dispatcher per fare il marshal in modo corretto.

Questo significa che se devo eseguire operazioni asincrone, ma non voglio legarmi a nessuna piattaforma(non solo UI) applicativa utilizzando questa classe(sperando che qualcuno abbia correttamente implementato una versione della classe SyncronizationContext) e non voglio avere problemi nel momento in cui dovrò eseguire la “callback” di finalizzazione dell’operazione, utilizzare l’astrazione attraverso SyncronizationContext mi risolve il mio problema di “generalizzazione”.

Se volete scrivere ancora meno codice e restare ad un livello ancora più alto sotto il namespace System.ComponentModel troviamo la classe AsyncOperationManager che fa un pò di lavoro per noi(come ad esempio salvarsi il context o inizializzare un context di default se non è presente, come ad esempio in una console application, il default context utilizza il thread pool nel Send/Post)attraverso un metodo “factory” di operazioni asincrone, permettendoci di focalizzarci di più sul problema di “business” e meno su quello “tecnico”, il System.ComponentModel.BackgroundWorker si basa su questa classe.

Esempio:

private void button1_Click(object sender, RoutedEventArgs e)

//creo il wrapper per l’operazione asyncrona, controlla se il context esiste o ne
//imposta uno di default che usa il ThreadPool .NET
AsyncOperation operation = AsyncOperationManager.CreateOperation(null);                   

System.Threading.ThreadPool.QueueUserWorkItem(a =>
               {
                   System.Threading.Thread.Sleep(5000);//simulo il carico di lavoro
                  

                   //Faccio il post dell’operazione sul contesto giusto e notifico il
                   //context sottostante
                   operation.PostOperationCompleted(o =>
                   {
                       this.button1.Content = o;
                   }, "Nuova Content del bottone");                                      
               });
}

Il metodo PostOperationComplete notifica in modo automatico il Context sottostante che l’operazione è terminata, in caso il context in questione avesse bisogno di saperlo per motivi architetturali(altrimenti avremmo dovuto chiamare prima il metodo Post() e poi il metodo OperationCompleted()). L’esempio non è esaustivo, ma da l’idea del senso per cui è stato creato per approfondimenti cercate nella guida o sul web, lo scopo di questo blog non è l’AsyncOperationManager.

Fortunatamente anche con le Task Parallel Library abbiamo la possibilità di sfruttare questo meccanismo in modo veramente semplice…il codice è più chiaro della spiegazione:

private void button1_Click(object sender, RoutedEventArgs e)
       {
              Task.Factory.StartNew(() =>
               {
                   Thread.Sleep(5000);//Simulo il carico
                   return "Nuova Content del bottone";
               })
               .ContinueWith(taskPrecedente =>
                   {
                       this.button1.Content = taskPrecedente.Result;
                   }, TaskScheduler.FromCurrentSynchronizationContext()
                   );

       }

Il metodo ContinueWith accetta come parametro tra i suoi overload un’oggetto di tipo TaskScheduler il quale ha un metodo statico FromCurrentSynchronizationContext che si occupa di creare un TaskScheduler del giusto tipo per il contesto in cui siamo.
Questo significa che l’operazione pesante(StartNew()) viene fatta in un thread separato e l’assegnazione del risultato(ContinueWith()) al nostro controllo viene “postato” sul thread di UI, grazie al fatto che abbiamo indicato uno scheduler conscio del fatto che siamo in un contesto di funzionamento per quanto riguarda il threading “particolare”(sotto le coperte utilizza il SyncronizationContext).

Non so cosa ne pensate, ma la sintassi per scrivere codice asincrono lato client con le TPL(task parallel library) è un bel passo avanti e alla portata di tutti…buon divertimento.

Vota questo post per primo

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: , , ,

Multithreading | .Net

Ottimizzazioni del compilatore e multithreading: loop invariant

by Marco 18. novembre 2009 10.58

Codice(C#,VS2008,.NET 3.5):

public class LoopInvariant
   {
       int count = 0;
       public void Cicla()
       {
           while (this.count == 0) {}
       }
       public void Cambia()
       {
           this.count = 1;
       }
   }

fino a qua, nulla di particolare, questa classe non fa un bel niente, semplicemente abbiamo 2 metodi, un metodo Cicla() che esegue una while verificando che count sia uguale a 0, poi abbiamo Cambia() un metodo che cambia il contatore e lo porta a 1.

Ora usiamo questa classe(console application):

static void Main(string[] args)
       {
           LoopInvariant var = new LoopInvariant();
           ThreadPool.QueueUserWorkItem(o =>
           {
               System.Threading.Thread.Sleep(5000);
               ((LoopInvariant)o).Cambia();
               Console.WriteLine("Valore modificato");
           }, var);
           var.Cicla();
           Console.WriteLine("Fine test");
           Console.ReadKey();
       }

qua cosa succede; niente di particolare: istanzio la classe LoopInvariant, faccio partire in un thread separato(attraverso il threadpool di .NET) un pezzo di codice che si blocca per 5 secondi(Sleep(5000)) dopo di che chiama il metodo Cambia() sulla mia istanza e stampa che lo ha fatto.

Bene, compiliamo(in release). Lanciamo(CTRL+F5)…passano i 5 secondi…6…7….8….9….?!?!?!?!?

LoopInvariant

cavolo succede?perchè non stampa “Fine test”?ho cambiato il valore di count…dovrebbe stampare “Fine test” (visto che la condizione count == 0 è falsa) e fermarsi li ad aspettare la pressione di un tasto…

Il problema è che il Jit compiler analizzando il metodo Cicla() applica una ottimizzazione chiamata Loop-invariant code motion, ovvero dal fatto che la variabile count non viene “toccata” all’interno del corpo del while, il compilatore può concludere che count è “loop invariant” e così metterlo in una variabile temporanea prima di iniziare il loop(in un registro del processore magari, così evitiamo di dover risolvere l’indirizzo in ram della variabile ad ogni ciclo migliorando le prestazioni) , questo comporta che quanto l’altro thread chiama Cambia() sulla stessa istanza la modifica non viene “vista” dal nostro metodo Cicla() portanto ad un loop infinito. Cosa che genera ancora più confusione è che se facciamo il debug(F5) di questo codice tutto funziona correttamente in quanto le ottimizzazioni sono disabilitate in configurazione DEBUG.

Per disabilitare questa ottimizzazione occorre dichiarare la nostra variabile count come volatile, questo rende il nostro count esplicitamente non “loop invariant” , come Joe Duffy spiega nel suo libro:

“Load and stores of volatile variables can never be introduced or removed, both in .NET and VC++, because they are assumed to be constantly changing. As such, they aren’t eligible for being considered loop invariant and hoisted outside of loops…”

quindi basta cambiare il nostro codice in questo modo:

public class LoopInvariant
   {
       volatile int count = 0;
       public void Cicla()
       {
           while (this.count == 0) {}
       }
       public void Cambia()
       {
           this.count = 1;
       }
   }

a questo punto se compiliamo(sempre in release altrimenti le ottimizzazioni vengono soppresse per supporto al debug) e lanciamo(CTRL+F5)…1…2….3..4..5…e

LoopInvariant2

adesso il programma funziona come ci aspettavamo…

Attenzione questo non è il solo effetto della keyword volatile, ricordo che leggere da un campo volatile(o usare Thread.VolatileRead) equivale logicamente ad una acquire fence, mentre scrivere un campo volatile(o usare Thread.VolatileWrite) equivale logicamente ad una release fence.

La classe LoopInvariant chiaramente non serve ad un piffero ed è li solo per didattica, ma questo ci fa capire che la programmazione multithread “aggiunge” una dimensione che oggi non è gestita in modo automatico dai compilatori, nel caso della loop invariant fare quella ottimizzazione non cambiava la semantica del programma, ma il danno lo abbiamo visto tutti.

Occhio…

Fonti: Concurrent Programming on Windows

Correntemente valutato 5.0 da 1 utenti

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: , ,

Concurrent Programming | Multithreading | Parallel Programming

STM.NET io voto si :-)

by Marco 27. agosto 2009 15.02

Segnalo un nuovo interessante progetto pubblicato dai DevLabs chiamato Software Transactional Memory.

Il progetto è nato con l’intento di fornire un modo “semplice” per permettere ad uno sviluppatore di dichiarare che l’esecuzione di una parte di codice debba essere eseguita in modo “atomico” e “transazionale”.

I problemi che questa nuova features risolverebbe non sono pochi e di semplice soluzione per uno sviluppatore che si appresta a scrivere codice parallelo.

Fino ad oggi se noi vogliamo sfruttare il parallelismo nel nostro codice, dobbiamo fare molta attenzione agli “stati condivisi” e all’ ”invarianza” che precluderebbero il buon funzionamento dei nostri algoritmi.

Oggi per fare questo ci possiamo affidare a delle tecniche di locking come ad esempio un bel Monitor, es(il codice è al minimo per chiarezza e non tiene conto di eventuali stati di errore che potrebbero portare a lock orfani, deadlock etc…):

Monitor.Enter(obj);

…//codice atomico

Monitor.Exit(obj);

Il codice non è complesso, ma come ho indicato tra parentesi i problemi che possono nascere sono molti e non così semplici e scontati da anticipare. Inoltre il nostro bel monitor(l’oggetto obj) deve essere trattato con cura onde evitare ulteriori danni(deadlock per esempio).

Come se non bastasse, il secondo problema non è legato al parallelismo di per sè, ma dalla parte di codice che sta tra l’Enter e l’Exit(critical region). In caso il codice al suo interno vada in errore potrebbe succedere che venga meno l’invarianza, cioè che il sucessivo thread che entra in quella regione atomica trovi stati logici inconsistenti precludento il corretto funzionamento del codice, es(il codice non è completo…fate finta che sia correttamente inserito in un try…catch):

Monitor.Enter(obj);

Decimal saldoConto = corrente – addebito;

//quì ad esempio potrebbe scatenarsi un’eccezzione che invalida le righe di codice sopra

Monitor.Exit(obj);

Come potete vedere questo errore è di una gravità mostruosa(sempre che non siate molto ricchi :-D) e solitamente prima di effettuare le operazioni servirebbe del codice che controlla che tutto sia coerente, ma questo non è sempre facile e dipende dalla problematica, che può essere molto complessa con molte casistiche da verificare.

Quello che STM potrebbe risolvere in modo stra-elegante sono proprio queste due problematiche, tutto con un nuovo semplice metodo(con il proprio statement shortcut) e una modifica al funzionamento del Jitter(quindi un framework con delle modifiche interne) es:

Atomic.Do(()=> { <statememts> });

o

atomic { <statememts> }

In questo modo non dobbiamo preoccuparti di trattare bene il nostro monitor(il rifermento all’oggetto obj del metodo Enter), ma la cosa più furba è il fatto che un errore nel blocco causa il rollback di tutte le modifiche fatte in quello scope mantenendo così l’invarianza senza dover fare controlli.

Vi lascio con un esempio preso dalla guida che rende tutto molto più chiaro:

class BankAccount
{
        private int m_balance;
        public void ModifyBalance(int amount) {
       atomic {
                       m_balance = m_balance + amount;
                       if (m_balance < 0)
                               throw new OverdraftException(m_balance, amount);
                  }
        }
}

Spettacolo!!!

Spero vivamente che il progetto non vada nel dimenticatoio, sarebbe una manna per tutti noi.

Qui trovate il portale del progetto.

Vota questo post per primo

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: , ,

.Net | Multithreading | Parallel Programming | Concurrent Programming

.NET ThreadPool 4.0(Beta 1)

by Marco 19. giugno 2009 15.00

La necessità di dover introdurre il concetto di “programmazione parallela” in modo più semplice possibile nelle menti di noi sviluppatori a causa dell’annuciato tramonto della famosa prima legge di Moore, ha portato il team del CLR a dover effettuare delle sostaziali modifiche nel funzionamento del ThreadPool che ci troveremo nel framework 4.0.

Le principali modifiche apportate sono le seguenti:

-Il ThreadPool funziona come una coda di workItem che vengono schedulati on demand, fino ad oggi la coda è stata implementata come una semplice linked list protetta da un Monitor. Questa tecnica diventa poco efficiente in caso di molti workload brevi, dovuti alla sincronizzazione della coda, inoltre il garbage collector è molto dispendioso nello scorrere questo tipo di struttura e liberarla in caso di necessità. Nel nuovo ThreadPool la coda è implementata con dei “chunk”(array) di workItem collegati. Questa tecnica permette di abbassare il lock necessari all’inserimento dei workItem(è necessario solo un’operazione atomica per incrementare l’indice nell’array) e permette al garbage collector di essere più efficente nella fase di collect.

-La modalità di scheduling utilizzerà l’algoritmo di “working stealing queue”, un meccanismo che permette di abbassare drasticamente il bisogno di lock e di mantere al massimo il consumo di CPU migliorando le prestazione complessive

-miglioria della funzionalità di “thread injection” che permette di regolare il numero di thread in gioco in modo dinamico così da utilizzare tutta la CPU disponibile

Alcune di queste features sono nate dalla stretta collaborazione con il PFX team(Parallel Framework Team) che fa largo uso di queste tecniche per ottimizzare il parallelismo all’interno delle Task Parallel Library.

Tutti questi nuovi meccanismi potete provarli nella Beta 1 di Visual Studio 2010 con il Framework 4.0 scaribili qui.

Vi lascio alcuni interessanti post e video sull’argomento

Daniel Moth post
Eric Eilebrecht post
Channel 9 video con Eric Eilebrech
Joe Duffy post

Vota questo post per primo

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: , , , ,

.Net | Multithreading

Parallel.For/ForEach e i System.Threading.Task

by Marco 9. giugno 2009 14.08

Il concetto che per avere migliori performance dobbiamo usare meno thread fisici possibili e di conseguenza meno risorse( magari 1 thread per core ), viene usato anche all’interno delle nuove Task Parallel Library ( .NET Framework 4.0 ) riguardo agli oggetti Task.

Cio significa che se abbiamo un ciclo for parallelo ( Parallel.For/ForEach ), non è detto che per ogni iterazione avremo un’oggetto Task che verrà schedulato, ma l’odierna implementazione prevede che ogni Task processi un chunk di operazioni ( iterazioni diciamo ), così da abbassare l’overhead della creazione e gestione degli stessi.

Ciò potrebbe portare a subdoli problemi, come nel caso in cui ci siano delle dipendenze tra le iterazioni che potrebbero mandare in deadlock il loop.

Se vi interessa un’approfondimento sulla tematica vi consiglio di dare una letta a questo post del pfxTeam :

http://blogs.msdn.com/pfxteam/archive/2009/05/26/9641563.aspx

“Axum” e il parallelismo implicito

by Marco 11. maggio 2009 16.13

Dai laboratori di ricerca di Microsoft è nato il progetto “Axum”, un’idea ambiziosa e molto interessante nata con lo scopo di permettere a noi sviluppatori di scrivere codice lasciando che il runtime possa “capire” e parallelizzare in modo autonomo l’esecuzione dello stesso, attraverso un linguaggio che si basa sull'architettura Web e sul principio dell'isolamento(fondamentale nella programmazione parallela).

Vi consiglio di dare un’occhiata al video che trovate nella pagina principale di presentazione del progetto al link http://msdn.microsoft.com/en-us/devlabs/dd795202.aspx

Credo che il “parallelismo implicito” sia l’unico modo per permettere a tutti di sfruttare veramente la potenza che i multicore ci mettono a disposizione senza dover leggere troppi libri :-)

Vota questo post per primo

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: , ,

Multithreading | Parallel Programming

In attesa delle TPL: Parallel.For me ;-)

by Marco 16. febbraio 2009 16.04

In attesa delle Task Parallel Library che saranno presenti nella versione 4.0 del Framework e che ora sono in CTP mi sono servito della sintassi che utilizzeremo in futuro per esprimere un ciclo For parallelo.

La firma di uno degli overload del For parallelo delle TPL ad oggi è questo:

public static void For(int fromInclusive,int toExclusive,Action<int> body)

Praticamente possiamo eseguire un’azione (Action<int> body) in parallelo più volte finchè la condizione fromInclusive < toExclusive viene verificata.

Il parametro di tipo intero che viene passato al body è l’indice del counter.

La sintassi con cui possiamo invocare il for parallelo è ad esempio questa:

System.Threading.Parallel.For(0,4,
               c =>
               {                                       
                   Console.WriteLine(System.Threading.Thread.CurrentThread.ManagedThreadId);                   
               });

il codice soprastante funziona se avete scaricato la CTP del giugno scorso e avete referenziato l’assembly System.Threading.
Il codice non fa niente di interessante al posto del Console.WriteLine(…) avreste dovuto mettere la parte di codice che volete venga eseguita in parallelo.

La mia versione del For parallelo fa uso della classe ThreadPool presente nel framework e che possiamo utilizzare già oggi, quindi utilizza un differente algoritmo di scheduling e non quello utilizzato dalle nuove TPL (molto più fine e complesso, magari vi spiegherò in qualche post futuro come funziona)…diciamo che hanno scritto qualche riga in più :-) e che risolvono molti più problemi di quelli che la mia classe risolve. Quindi il funzionamento beneficia delle gioie e soffre i dolori che il funzionamento della classe ThreadPool porta con se.

La classe che ho scritto e utilizzato è la seguente:

public class Parallel
{
    public static void For(int fromInclusive, int toExclusive, System.Action<int> body)
    {
        ThreadPool.QueueUserWorkItem(f =>
        {
            for (int i = fromInclusive; i < toExclusive; i++)
            {
                ThreadPool.QueueUserWorkItem(p =>
                    {
                        Data pl = (Data)p;
                        pl.Action(pl.Count);                   
                    }
                    , new Data() { Action = body, Count = i });
            }
        }, null);       
    }  
    class Data
    {
        public Action<int> Action { get; set; }
        public int Count { get; set; }
    }
}

Il codice di invocazione è il medesimo delle TPL basta evitare di usare la parte System.Threading, ma scrivere solamente:

Parallel.For(0,4,
               c =>
               {                                       
                   Console.WriteLine(System.Threading.Thread.CurrentThread.ManagedThreadId);                   
               });

la classe deve essere chiaramente visibile.
Il codice non è stato testato "seriamente" quindi potrebbe essere afflitto da bugs e non tratta problematiche come ad esempio gestione delle eccezioni, sospensione del task, sincronizzazione, partizionamento o altre problematiche legate al parallelismo che sono state gestite all’interno delle TPL…per il mio problema questa classe è più che sufficiente, il giorno che rilasceranno le TPL toglierò la mia classe e avrò bisogno solo di referenziare l’assembly con le nuove classi per sfruttare il lavoro del team di Microsoft.
Se trovate qualche bugs o questa soluzione non risolve il vostro problema o ne crea di nuovi contattatemi qui che magari ne parliamo.

 Scarica la solution di esempio

Disclaimer
Le opinioni espresse in questo blog sono mie opinioni personali.

© Copyright 2010 Knowledge.CreateAsync()