Passa ai contenuti principali

I Tasks del .NET Framework 4.0

In questo post vorrei porre l’attenzione su alcune classi, introdotte nel Framework 4.0, che semplificano la scrittura di codice multithread.

Si tratta delle classi per la gestione dei task cioè di operazioni asincrone su thread separati.

Queste classi fanno parte della Task Parallel Library (TPL) introdotta con il Framework 4.0 e si trovano all’interno del namespace è System.Threading.Task e sono, in dettaglio, le seguenti:

  • Task : Rappresenta un'operazione asincrona.
  • Task(Of TResult) : Rappresenta un'operazione asincrona in grado di restituire un valore.
  • TaskCanceledException : Rappresenta un'eccezione utilizzata per comunicare l'annullamento di un'attività.
  • TaskCompletionSource(Of TResult) : Rappresenta il lato producer di un oggetto Task(Of TResult) non associato a un delegato e che consente l'accesso al lato consumer tramite la proprietà Task.
  • TaskExtensions : Fornisce un set di metodi statici (Shared in Visual Basic) per l'utilizzo di tipi specifici di istanze di Task.
  • TaskFactory : Fornisce supporto per la creazione e la pianificazione di oggetti Task.
  • TaskFactory(Of TResult) : Fornisce supporto per la creazione e la pianificazione di oggetti Task(Of TResult).
  • TaskScheduler : Rappresenta un oggetto che gestisce le operazioni di basso livello relative all'accodamento delle attività nei thread.
  • TaskSchedulerException : Rappresenta un'eccezione utilizzata per comunicare un'operazione non valida eseguita da un oggetto TaskScheduler.
  • UnobservedTaskExceptionEventArgs : Fornisce i dati dell'evento generato quando l'eccezione di un oggetto Task in cui si è verificato un errore non viene osservata.

Classi Task e Task(Of Result)

Entrambe le classi permettono la creazione e gestione di attività multithread asincrone. La classe Task è utilizzata per attività che non hanno la necessità di restituire un valore mentre la Task(Of Result) può essere utilizzata quando dobbiamo eseguire attività asincrone al cui termine abbiamo la necessità di un valore di ritorno. La Task(Of Result) deriva dalla Task.

Le due classi mettono a disposizione una serie di costruttori che ci permettono poter eseguire task in tantissime modalità differenti.

Cominciamo a vedere qualche esempio. Negli esempi riportati di seguito alterneremo la classe Task e la classe Task(Of Result) in modo da far vedere le funzionalità di tutte e due.

Primo esempio molto semplice prevede l’esecuzione di una Sub denominata LongRunJob e l’attesa da parte del programma principale:

  1. Private Sub SimpleTask()
  2.     Dim task = New Threading.Tasks.Task(New Action(AddressOf LongRunJob))
  3.     Console.WriteLine("Start LongRunJob")
  4.     task.Start()
  5.     task.Wait()
  6.     Console.WriteLine("End LongRunJob")
  7.     Console.ReadLine()
  8. End Sub
  9.  
  10. Private Sub LongRunJob()
  11.     Console.WriteLine("{0:HH.mm.ss} - LongRunJob Iniziato", DateTime.Now)
  12.     Threading.Thread.Sleep(10000)
  13.     Console.WriteLine("{0:HH.mm.ss} - LongRunJob Completato", DateTime.Now)
  14. End Sub

In questo esempio è creato un oggetto di classe Task a cui è passato il delegate del codice da eseguire (LongRunJob), è eseguito il task (cioè il codice all’interno della sub LongRunJob) e atteso il completamento dello stesso. Il metodo Start della classe Task (e della classe Task(Of Result)) prevede due possibili firme: la prima è quella dell’esempio precedente mentre la seconda permette la gestione di task schedulati.

Nell’esempio precedente abbiamo atteso il completamento del task incondizionatamente ma possiamo, se serve, attendere per un determinato timeout. Nel successivo esempio vediamo come utilizzare un task che restituisce un valore e come attendere il completamento per un determinato timeout:

  1. Private Sub ResultTask()
  2.     Dim sleepTime = New Random(DateTime.Now.Millisecond).Next(10000)
  3.     Dim task = New Threading.Tasks.Task(Of DateTime)(Function()
  4.                                                          Return LongRunJob(sleepTime)
  5.                                                      End Function)
  6.     Console.WriteLine("Start LongRunJob")
  7.     task.Start()
  8.     If Not task.Wait(5000) Then
  9.         Console.WriteLine("Timeout scattato")
  10.     Else
  11.         Dim result = task.Result
  12.         Console.WriteLine("End LongRunJob- {0:HH.mm.ss}", result)
  13.     End If
  14.  
  15.     Console.ReadLine()
  16. End Sub
  17.  
  18. Private Function LongRunJob(ByVal sleepTime As Integer) As DateTime
  19.     Console.WriteLine("{0:HH.mm.ss} - LongRunJob Iniziato", DateTime.Now)
  20.     Threading.Thread.Sleep(sleepTime)
  21.     Console.WriteLine("{0:HH.mm.ss} - LongRunJob Completato", DateTime.Now)
  22.     Return DateTime.Now
  23. End Function

In questo esempio è avviato un task composto da una funzione che accetta un intero (numero di millisecondi di attesa) e restituisce una data (data di completamento). Questa volta il costruttore del task utilizza una espressione lambda per definire cosa fa il task stesso. Per attendere il completamento del task, viene utilizzata la firma del metodo Wait() che prevede un numero di millisecondi di attesa. Scaduto il timeout, il metodo Wait() restituisce false. Nell’esempio precedente, se non scade il timeout, viene recuperato il valore di ritorno attraverso la proprietà Result.

Le due classi Task prevedono alcune proprietà utili:

  • AsyncState : Ottiene l'oggetto stato fornito quando è stato creato Task oppure null se non ne è stato fornito alcuno. L’oggetto AsyncState ha la stessa funzionalità del corrispondente per i thread classici;
  • CreationOptions : Ottiene l'oggetto TaskCreationOptions utilizzato per creare questa attività. L’utilizzo di tale proprietà verrà spiegato in un esempio successivo;
  • CurrentId : Restituisce l'ID univoco dell'oggetto Task attualmente in esecuzione;
  • Exception : Ottiene l'oggetto AggregateException che ha causato l'interruzione anomala dell'oggetto Task.Se l'oggetto Task è stato completato correttamente o non ha ancora generato alcuna eccezione, verrà restituito Nothing;
  • Factory : Fornisce l'accesso ai metodi factory per la creazione di istanze di Task e Task(Of TResult);
  • Id : Ottiene un ID univoco per questa istanza di Task;
  • IsCanceled : Ottiene un valore che indica se l'esecuzione di questa istanza di Task è stata completata poiché è stata annullata;
  • IsCompleted : Ottiene un valore che indica se questo oggetto Task è stato completato;
  • IsFaulted : Ottiene un valore che indica se l'oggetto Task è stato completato a causa di un'eccezione non gestita;
  • Status : Ottiene l'oggetto TaskStatus di questa attività.

Come possiamo osservare, ogni Task dispone di un ID univoco assegnato dal sistema che ci permette di caratterizzarlo e riconoscerlo.

Nel precedente esempio abbiamo visto come attendere il completamento di un task per un certo timeout. Osserviamo che, nel momento in cui il Wait() ritorna con risultato False (cioè il timeout è scaduto) il task non viene automaticamente interrotto e continua la sua opera. Se abbiamo la necessità di interromperlo, dobbiamo ricorrere ad un CancellationToken nel seguente modo:

  1. Private Sub ResultTaskAborted()
  2.     Dim sleepTime = New Random(DateTime.Now.Millisecond).Next(10000)
  3.     Dim tokenSource = New CancellationTokenSource()
  4.     Dim cancToken = tokenSource.Token
  5.  
  6.     Dim task = New Threading.Tasks.Task(Of DateTime)(Function()
  7.                                                          Return LongRunJob(sleepTime, cancToken)
  8.                                                      End Function, cancToken)
  9.     Console.WriteLine("Start LongRunJob")
  10.     task.Start()
  11.     If Not task.Wait(5000) Then
  12.         tokenSource.Cancel()
  13.         Console.WriteLine("Timeout scattato")
  14.     Else
  15.         Dim result = task.Result
  16.         Console.WriteLine("End LongRunJob- {0:HH.mm.ss}", result)
  17.     End If
  18.     Console.ReadLine()
  19. End Sub
  20.  
  21. Private Function LongRunJob(ByVal sleepTime As Integer, ByVal token As CancellationToken) As DateTime
  22.     Dim startTime = DateTime.Now
  23.     Console.WriteLine("{0:HH.mm.ss} - LongRunJob Iniziato", startTime)
  24.     While (DateTime.Now.Subtract(startTime).TotalMilliseconds <= sleepTime)
  25.         If token.IsCancellationRequested Then
  26.             Return DateTime.MinValue
  27.         End If
  28.         Threading.Thread.Sleep(25)
  29.     End While
  30.     Console.WriteLine("{0:HH.mm.ss} - LongRunJob Completato", DateTime.Now)
  31.     Return DateTime.Now
  32. End Function

Il token di annullamento è generato utilizzando una “sorgente di token” (classe CancellationSourceToken) la quale ci fornisce anche il metodo per richiedere la cancellazione (metodo Cancel()). Il codice del nostro task controlla l’eventuale richiesta di annullamento (IsCancellationRequest=true) e a seguito di questo chiude il proprio lavoro.

Se il nostro task vuole comunicare al mondo esterno che sta chiudendo in maniera anomala, si può utilizzare il metodo ThrowIfCancellationRequested() che è equivalente a generare un eccezione di tipo OperationCanceledException nei thread che sono in waiting sul task.

Il token di annullamento può essere utilizzato anche in altre tipologie di oggetti quali Thread o BackgroundWorker (per questo motivo si trova all’interno di System.Thread e non di System.Thread.Tasks).

Nel costruttore della classe Task (o Task(Of Result)) possiamo utilizzare anche l’enumerazione TaskCreationOptions che ci consente di definire il comportamento delle nostre attività. I valori possibili per l’enumerazione TaskCreationOptions sono i seguenti:

  • None : Specifica che deve essere utilizzato il comportamento predefinito;
  • PreferFairness : Indicazione fornita a un oggetto TaskScheduler affinché pianifichi un'attività nel modo più giusto possibile, ovvero garantendo che le attività pianificate prima abbiano più possibilità di essere eseguite prima delle attività pianificate in un secondo momento;
  • LongRunning : Specifica che un'attività sarà un'operazione a bassa precisione di lunga durata. Fornisce a TaskScheduler un'indicazione in merito alla possibilità di dover ricorrere all'oversubscription;
  • AttachedToParent : Specifica che un'attività è connessa a un elemento padre nella gerarchia delle attività.

Se vogliamo,quindi, far eseguire il nostro task LongRunJob come un task a lunga durata, ci basta scrivere:

  1. Dim task = New Threading.Tasks.Task(New Action(AddressOf LongRunJob), Threading.Tasks.TaskCreationOptions.LongRunning)

L’enumerazione TaskCreationOptions è un enumerazione decorate con l’attributo Flag() e, quindi, è una enumerazione il cui valore può essere anche una combinazione dei valori indicate nella precedente tabella.

Classi TaskFactory e TaskFactory(Of Result)

Le classi TaskFactory e TaskFactory(Of Result) forniscono dei metodi che permettono la creazione dei task:

  1. Private Sub SimpleFactoryTask()
  2.     Console.WriteLine("Start LongRunJob")
  3.     Dim factory = New Threading.Tasks.TaskFactory
  4.     Dim task = factory.StartNew(New Action(AddressOf LongRunJob))
  5.     task.Wait()
  6.     Console.WriteLine("End LongRunJob")
  7.     Console.ReadLine()
  8. End Sub

Nel precedente esempio, tramite la classe TaskFactory, creiamo e avviamo il nostro task.

Classe TaskScheduler

Una delle funzionalità più interessanti dei tasks di .NET Framework 4.0 è la possibilità di realizzare in maniera molto semplice uno scheduler delle attività gestito quasi completamente dal framework. Per fare questo ci viene in aiuto la classe TaskScheduler che rappresenta la classe base su cui lavorare per creare il nostro gestore di attività.

Derivando la classe TaskScheduler e implementando opportunamente i metodi della stessa possiamo realizzare il nostro gestore delle attività. Un esempio di implementazione è riportato all’indirizzo http://msdn.microsoft.com/it-it/library/ee789351.aspx in cui viene mostrato come realizzare uno scheduler che limita il grado di concorrenza sulle attività.

Commenti

Post popolari in questo blog

MVP Reconnect …… ovvero quando entri nella “famigghia” resti sempre nella “famigghia”!!!

Ma di che “famigghia” stiamo parlando!!!!

Fermi tutti, non si tratta di robe strane o sette segrete o affari malavitosi….stiamo parlando della grande famiglia dei Microsoft MVP.

Per chi non sapesse cosa sono i Microsoft MVP, vi consiglio di fare un giro sul sito ufficiale del programma (link), ma, volendolo spiegare in pochisime parole, si tratta di un riconoscimento che Microsoft da a persone che si distinguono per il loro impegno, aiutando gli altri ad ottenere il massimo grazie alle tecnologie Microsoft. Si tratta di persone, non dipendenti Microsoft, che mettono la loro passione, il loro tempo, la loro buona volontà per la divulgazione e la condivisione della conoscenza. Non necessariamente (come qualcuno erroneamente sostiene, evidentemente non conoscendo le basi del programma) si tratta di professionisti nel termine letterale del termine ma si tratta comunque di un gruppo di persone che sacrifica un pò del suo tempo (e, a volte, vi assicuro neanche pò!!!) per la sua passione.

Pe…

Nuova versione del Band SDK

E’ di ieri l’annuncio del rilascio della nuova versione dell’SDK per il Microsoft Band.
Si tratta della versione 1.3.10417 (la precedente e, prima della serie, era la 1.3.10219 preview).
Maggiori informazioni, download dell’SDK per le tre piattaforme Windows Phone, iOS e Android all’indirizzo http://developer.microsoftband.com/.
Allo stesso indirizzo potrete trovare anche la documentazione.
Nei mesi scorsi mi sono gia’ occupato della precedente versione e questi sono i post che ne parlano:
Microsoft Band SDK Preview - First LookMicrosoft Band SDK Preview - ”Hello Band”Microsoft Band SDK Preview - Accesso ai sensoriMicrosoft Band SDK Preview - TileMicrosoft Band SDK Preview - NotificheMicrosoft Band SDK Preview - Personalizzazione
Gli argomenti trattati e il codice proposto dovrebbe, ad una prima lettura delle nuove funzionalita’ inserite, essere ancora valido e funzionante ma nei prossimi giorni prendero’ in esame tutti gli argomenti dei precedenti post e vedremo cosa cambia e cosa e’ …

Template di progetto per sviluppare applicazioni WPF con Intel® RealSense™

E’ disponibile, nella gallery di Visual Studio, la prima versione del mio template di progetto per applicazioni WPF scritte in C# che permette di realizzare applicazioni con l’SDK di Intel® RealSense™.Il template si può scaricare direttamente all’interno Visual Studio utilizzando il tool “Extensions and Updates”oppure all’indirizzo https://visualstudiogallery.msdn.microsoft.com/1c36ecfd-8c00-4aee-b20c-a1726ab6424dIl template esegue le seguenti operazioni per voi:Aggiunge la reference all’assembly libpxcclr.cs.dll (nelle due distinte versioni per x86 e x64);Aggiunge lo script di post build per copiare la libreria libpxccpp2c.dll dalla cartella dell’SDK alla cartella bin del vostro progetto.Una volta creato il progetto dovete rimuovere la configurazione di compilazione AnyCPU (che non ha più senso) dalla vostra solution e sarete pronti per sviluppare con Intel® RealSense™.Ovviamente dovete installare l’SDK che potete scaricare all’indirizzo https://software.intel.com/en-us/intel-realsen…