Passa ai contenuti principali

La classe Lazy(Of T)

Una interessante classe inserita nella versione 4.0 del framework è la Lazy(Of T).

Si tratta di un generic il cui scopo è quello di permettere la creazione di oggetti differita.

Uno scenario tipico che può capitare quando sviluppiamo è quello di avere una nostra classe che occupa molte risorse (memoria, accesso a database, accesso a filesystem, e via discorrendo) e desiderare che tale classe venga istanziata solo quando serve.

In più, alcune volte, sarebbe anche comodo poter avere un accesso concorrente a tale istanza.

Se il primo requisito è facilmente implementabile con un manciata di righe di codice, il secondo non è altrettanto banale.

La classe Lazy(Of T) ci viene in aiuto perché è, di fatto, un wrapper che gestisce la creazione dell’istanza del tipo T e il thread safe in maniera nativa.

La classe Lazy prevede una serie di costruttori il più semplice dei quali non prevede argomenti. Sia MyClass la nostra classe di cui vogliamo gestire la creazione differita:

  1. Public Class [MyClass]
  2.  
  3.     Public Sub New()
  4.         ConsoleHelper.WriteConsole("MyClass - New")
  5.     End Sub
  6.  
  7.     Public Sub New(i As Integer)
  8.         ConsoleHelper.WriteConsole(String.Format("MyClass - New with {0}", i))
  9.     End Sub
  10.  
  11.     Public Sub MyMethod()
  12.         ConsoleHelper.WriteConsole("MyClass - MyMethod")
  13.     End Sub
  14. End Class

La classe ConsoleHelper permette di utilizzare la Console per scrivere dei messaggi a cui viene anteposto un timestamp. Nei costruttori della classe MyClass ho inserito la scrittura dei messaggi per poter, effettivamente, capire quando il costruttore stesso viene invocato.

Prendiamo in esame il seguente pezzo di codice:

  1. Sub Main()
  2.     ConsoleHelper.WriteConsole("CreateSimpleLazy - Before")
  3.     Dim lazy = CreateSimpleLazy()
  4.     ConsoleHelper.WriteConsole("CreateSimpleLazy - After")
  5.     Threading.Thread.Sleep(1000)
  6.     lazy.Value.MyMethod()
  7.     Console.ReadLine()
  8. End Sub

e il seguente:

  1. Public Function CreateSimpleLazy() As Lazy(Of [MyClass])
  2.     Dim retVal = New Lazy(Of [MyClass])
  3.  
  4.     Return retVal
  5. End Function

Il metodo CreateSimpleLazy altro non fa che istanziare un oggetto di classe Lazy(Of MyClass) e restituirlo al chiamante.

Nel metodo chiamante eseguiamo la chiamata al metodo MyMethod della MyClass in maniera differita (cioè solo dopo aver atteso 1 secondo dall’istanziazione dell’oggetto Lazy).

Il risultato a video è il seguente:

SNAGHTML122e0a4b

Come possiamo osservare il costruttore della classe MyClass (riga “MyClass – new”) viene richiamato solo nel momento in cui si esegue l’istruzione lazy.Value.MyMethod e non quando viene creato l’oggetto Lazy.

In effetti la classe Lazy espone l’istanza della classe che wrappa tramite la proprietà Value all’interno della quale viene gestito l’effettivo richiamo del costruttore. Se l’oggetto wrappato non è stato ancora istanziato, nel momento in cui viene utilizzata la proprietà Value, viene richiamato il costruttore di default della classe wrappata.

Ma se la nostra classe non prevede un costruttore di default?

In questo caso ci viene in aiuto un altro costruttore della Lazy la cui sintassi è la seguente:

  1. Public Sub New(
  2.               valueFactory As Func(Of T)
  3.               )

Questo costruttore prevede che si possa utilizzare una Func(Of T) come parametro (ad esempio una lambda expression), all’interno della quale creeremo l’istanza della nostra classe e la restituiremo alla Lazy che la gestirà in maniera del tutto analoga al caso precedente:

  1. Sub Main()
  2.     ConsoleHelper.WriteConsole("CreateParametricLazy - Before")
  3.     Dim parametricLazy = CreateParametricLazy(100)
  4.     ConsoleHelper.WriteConsole("CreateParametricLazy - After")
  5.     Threading.Thread.Sleep(1000)
  6.     parametricLazy.Value.MyMethod()
  7.  
  8.     Console.ReadLine()
  9. End Sub

In questo caso utilizziamo il costruttore della classe MyClass che prevede un parametro intero:

  1. Public Function CreateParametricLazy(i As Integer) As Lazy(Of [MyClass])
  2.     Dim retVal = New Lazy(Of [MyClass])(Function()
  3.                                             Return New [MyClass](i)
  4.                                         End Function)
  5.  
  6.     Return retVal
  7. End Function

Il risultato che otteniamo è:

SNAGHTML1236d622

La classe Lazy, per default, ThreadSafe cioè permette accesso contemporaneo (e sicuro) alla proprietà Value da thread differenti. La modalità di default è la LazyThreadSafetyMode .ExecutionAndPubblication che prevede che un solo thread possa eseguire l’inizializzazione di T ma che tale istanza sia poi disponibili a tutti i thread che la richiedono.
Questo significa che se più thread tentano di eseguire l’inizializzazione della classe T, uno solo di questi ha accesso al costruttori e gli altri attendono l’effettiva creazione della stessa.

Questo fatto comporta che se la nostra classe utilizza dei meccanismi di thread safe interni e questi non lavorano correttamente, si potrebbero creare dei dedlock da cui non si può uscire.

Possiamo stabilire il comportamento della classe Lazy rispetto all’accesso contemporaneo con opportuni costruttori della stessa che ci permettono di avere un’istanza non ThreadSafe oppure di stabilire esattamente il LazyThreadSafetyMode da utilizzare. I possibili valori di questa enumerazione sono:

  • None : non thread safe;
  • ExecutionAndPubblication: un unico thread può inizializzare T la cui istanza viene utilizzata da tutti i thread. E’ una modalità ThreadSafe;
  • PublicationOnly : più thread possono inizializzare T, il primo che riesce rende l’istanza disponibile e viene utilizzata dagli altri. Eventuali altre istanze vengono scartate. E’ una modalità ThreadSafe.

La tipologia di ThreadSafe scelta influenza anche il comportamento della classe Lazy rispetto alle eventuali eccezioni generate dal costruttore della classe contenuta.

Se stiamo utilizzando il costruttore di default, l’eventuale eccezione generata viene immediatamente sollevata (a prescindere da quale valore di LazyThreadSafetyMode  abbiamo scelto) mentre se stiamo utilizzando un costruttore con parametri l’eventuale eccezione generata si comporta in maniera differente in base al valore di LazyThreadSafetyMode scelto.

Se si è scelto PublicationOnly (valore di default) le eccezioni vengono immediatamente generate nel costruttore della Lazy (come nel caso precedente), mentre se stiamo utilizzando uno delle altre sue modalità di LazyThreadSafetyMode, allora le eccezioni vengono salvate in cache e proposte nel momento in cui viene invocata la proprietà Value.

Infine, la classe Lazy(Of T) prevede la proprietà IsValueCreated che permette di conoscere se l’istanza di T è stata già creata o meno.

 

Commenti

Post popolari in questo blog

VB.NET for Dummies: Gli Eventi

Vorrei iniziare con questo post una serie dedicata ad aspetti di VB.NET di base che possono essere utile a coloro che si avvicinano al mondo .NET e che, in genere, non vengono trattati a livello base. La serie di post non ha la pretesa di essere assolutamente esaustivi sugli argomenti che tratterò In questo primo post parleremo degli eventi. Cosa sono e a cosa servono Un evento è la “notifica” dell’accadimento di qualcosa. Quando, ad esempio, premiamo un bottone della finestra di un’applicazione, dietro le quinte, il bottone stesso “notifica” al mondo circostante che qualcuno, in quell’istante, lo ha premuto. Sta, poi, al mondo circostante preoccuparsi di “intercettare” l’evento stesso per gestirlo (tramite un gestore di evento). Attenzione a non confondere la pressione del tasto con la “notifica” della pressione del tasto: l’evento è la “notifica” dell’accadimento, non l’accadimento stesso. Ma a cosa serve tutto ciò? Utilizzare gli eventi è un modo per disaccoppiare ...

VB.NET: SplashScreen con effetto fade-in

In questo post vorrei proporvi un modo per realizzare una splash screen per le nostre applicazioni Windows Form che appare progressivamente con un effetto fade. Supponiamo di avere il nostro progetto VB.NET in una soluzione Visual Studio 2008 in cui abbiamo il sorgente della nostra applicazione Windows Form. Inseriamo una splash screen utilizzando il menù Progetto->Aggiungi Nuovo Elemento e selezionando il tipo di elemento “Schermata Iniziale” A questo punto Visual Studio creerà, automaticamente, la schermata iniziale che possiamo personalizzare graficamente come vogliamo. Per poter fare in modo che questa finestra appaia nel momento in cui avviamo l’applicazione, è necessario aprire le proprietà del progetto e impostare la maschera di avvio: In questo modo, all’avvio dell’applicazione, la schermata appare immediatamente e scompare un attimo prima della visualizzazione della finestra dell’applicazione. Possiamo far apparire la schermata iniziale con un ef...

Alla scoperta del Kinect: presentazioni e convenevoli

Oggi è arrivato un Kinect nuovo nuovo su cui cominciare a fare sperimentazione ed ho, quindi, deciso di condividere tutto ciò che scopro, le cavolate che faccio e i segreti che scopro con chi mi segue. I post che cercherò di scrivere con frequenza sono post di un neofita che si avvicina all’”Aggeggio” e che quindi sono diretti a chi nulla dell’argomento. Gli esperti troveranno noiosi e banali questi post, ma non si può aver tutto dalla vita. Per cominciare, scartato l’”Aggeggio” ho cominciato a preparare l’ambiente di sviluppo: 1) Visual Studio 2010 Ultimate (che uso normalmente ma che non è necessario in quanto si può sviluppare tranquillamente con la express edition); 2) Kinect SDK, scaricabile all’indirizzo http://research.microsoft.com/en-us/um/redmond/projects/kinectsdk/download.aspx 3) DirectX Software Development Kit scaricabile all’indirizzo http://www.microsoft.com/download/en/details.aspx?displaylang=en&id=6812 A questo punto vi basta connettere il Kinect al...