.net 4 et Lazy-loading thread-safe : un nouveau type bien pratique
.net 4.0 a introduit plein de petites nouveautés au sein de mscorlib : l’assembly principale du framework .net.
L’une des nouveautés que j’aime bien et le type Lazy. Ce type permet de faire du lazy-loading simplement et permet surtout de le faire de façon thread-safe.
Tout d’abord, revenons sur le principe de lazy-loading. Il s’agit d’un design pattern permettant le chargement ou la création de la valeur d’une propriété à la demande.
Imaginons que vous disposez d’une classe Pouet qui comporte plusieurs propriétés dont la propriété HeavyProperty. Comme son nom l’indique, cette propriété est très couteuse, on va donc vouloir créer la valeur seulement lorsque l’on en a vraiment besoin.
Le code ci-dessous est un exemple d’une propriété en lazy-loading :
public class Pouet
{
private HeavyObject _heavyProperty;
public HeavyObject HeavyProperty {
get {
if (this._heavyProperty != null) {
this._heavyProperty = new HeavyObject();
}
return this._heavyProperty;
}
}
}
Bien que fonctionnel, ce code n’est pas parfait : il n’est pas thread safe. Si 2 threads accèdent simultanément à la propriété, alors le constructeur de HeavyObject peut être appelé deux fois.
Souvent, ce n’est pas gênant. Si l’instance de Pouet est utilisé par un seul thread, il est alors inutile de les rendre thread-safe. De plus, si la valeur de la propriété n’a pas besoin d’être unique et si l’initialisation de l’objet n’est pas très couteux, inutile de sortir l’artillerie lourde. Dans les autres cas, ce comportement peut poser bien des soucis.
Avec .net 3.5, on pouvait utiliser le mot clé lock ou la classe Monitor. Personnellement, je préfère utiliser la classe Monitor, cela permet de définir un timeout et ainsi éviter les deadlock. En interne, le mot clé lock utilise également la classe Monitor.
Au final, une propriété en lazy-loading thread-safe s’écrit comme ca en .net 3.5 :
public class Pouet
{
private Object _heavyPropertyLock = new Object();
private HeavyObject _heavyProperty;
public HeavyObject HeavyProperty {
get {
if (this._heavyProperty == null) {
if (!Monitor.TryEnter(this._heavyPropertyLock, 4000)) {
throw new Exception("can't acquire lock");
}
try {
// double check. Check if another thread didn't create the HeavyObject
// while this thread was waiting for aquiring the lock object
if (this._heavyProperty == null) {
this._heavyProperty = new HeavyObject();
}
}
finally {
Monitor.Exit(this._heavyPropertyLock);
}
}
return this._heavyProperty;
}
}
}
Comme on peut le voir, cela devient relativement lourd.
Avec .net 4.0, il existe le type Lazy qui va permettre de simplifier tout ça. Voici le même code que ci-dessus avec le type Lazy :
public class Pouet
{
private Lazy<HeavyObject> _heavyProperty = new Lazy<HeavyObject>(() => new HeavyObject());
public HeavyObject HeavyProperty {
get {
return this._heavyProperty.Value;
}
}
}
Plutot simple non ?
Cela fonctionne ainsi. Dans l’exemple ci-dessus, le constructeur du type Lazy prend un Func<T>. Ce Func<T> doit retourner une instance de l’objet que l’on veut initialiser. Lors du premier accès à la propriété Value, le type Lazy va executer le Func<T> tout en faisant en s’occupant de la synchronisation des threads afin d’éviter que 2 threads initialisent simultanément l’objet.
A noter qu’il existe plusieurs constructeurs. Il existe un constructeur vide, dans ce cas, le type Lazy va initialiser le type T à l’aide de son constructeur par défaut.
Un autre constructeur prend en entrée un LazyThreadSafetyMode.

Ce paramètre permet d’indiquer la façon dont le type Lazy doit gérer la concurrence de thread. Il s’agit d’une enum qui a 3 valeurs :
- None : le type Lazy ne garantit pas que l’initialisation soit effectuée qu’une seule fois
- PublicationOnly : Plusieurs threads peuvent initialiser l’objet. Le premier thread qui termine l’initialisation définit la propriété Value, tous les threads utiliseront cette valeur.
- ExecutionAndPublication : Seul un thread peut initialiser l’objet. Valeur par défaut.
Connaissiez-vous le type Lazy<T> ? L’aviez-vous déjà utilisé ?