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:
- Public Class [MyClass]
- Public Sub New()
- ConsoleHelper.WriteConsole("MyClass - New")
- End Sub
- Public Sub New(i As Integer)
- ConsoleHelper.WriteConsole(String.Format("MyClass - New with {0}", i))
- End Sub
- Public Sub MyMethod()
- ConsoleHelper.WriteConsole("MyClass - MyMethod")
- End Sub
- 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:
- Sub Main()
- ConsoleHelper.WriteConsole("CreateSimpleLazy - Before")
- Dim lazy = CreateSimpleLazy()
- ConsoleHelper.WriteConsole("CreateSimpleLazy - After")
- Threading.Thread.Sleep(1000)
- lazy.Value.MyMethod()
- Console.ReadLine()
- End Sub
e il seguente:
- Public Function CreateSimpleLazy() As Lazy(Of [MyClass])
- Dim retVal = New Lazy(Of [MyClass])
- Return retVal
- 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:
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:
- Public Sub New(
- valueFactory As Func(Of T)
- )
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:
- Sub Main()
- ConsoleHelper.WriteConsole("CreateParametricLazy - Before")
- Dim parametricLazy = CreateParametricLazy(100)
- ConsoleHelper.WriteConsole("CreateParametricLazy - After")
- Threading.Thread.Sleep(1000)
- parametricLazy.Value.MyMethod()
- Console.ReadLine()
- End Sub
In questo caso utilizziamo il costruttore della classe MyClass che prevede un parametro intero:
- Public Function CreateParametricLazy(i As Integer) As Lazy(Of [MyClass])
- Dim retVal = New Lazy(Of [MyClass])(Function()
- Return New [MyClass](i)
- End Function)
- Return retVal
- End Function
Il risultato che otteniamo è:
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