.NET Framework ConcurrentDictionary augmenté avec Lazy'1 réduit le calcul dupliqué


Exemple

Problème

ConcurrentDictionary brille quand il s'agit de renvoyer instantanément des clés existantes du cache, la plupart du temps sans verrou, et de se disputer à un niveau granulaire. Mais que se passe-t-il si la création de l’objet est vraiment coûteuse, dépassant le coût de la commutation de contexte et que certains incidents de cache se produisent?

Si la même clé est demandée à plusieurs threads, l'un des objets résultant des opérations en collision sera éventuellement ajouté à la collection, et les autres seront jetés, gaspillant la ressource du processeur pour créer l'objet et la ressource mémoire pour stocker temporairement l'objet . D'autres ressources pourraient également être gaspillées. C'est vraiment mauvais.

Solution

Nous pouvons combiner ConcurrentDictionary<TKey, TValue> avec Lazy<TValue> . L'idée est que la méthode GetOrAdd de ConcurrentDictionary ne peut que renvoyer la valeur réellement ajoutée à la collection. Les objets perdants de Lazy pourraient aussi être perdus dans ce cas, mais cela ne pose pas de problème, car l'objet Lazy lui-même est relativement peu coûteux. La propriété Value de Lazy Lazy n'est jamais demandée, car nous ne pouvons demander que la propriété Value de celle ajoutée à la collection, celle renvoyée par la méthode GetOrAdd:

public static class ConcurrentDictionaryExtensions
{
    public static TValue GetOrCreateLazy<TKey, TValue>(
        this ConcurrentDictionary<TKey, Lazy<TValue>> d,
        TKey key,
        Func<TKey, TValue> factory)
    {
        return
            d.GetOrAdd(
                key,
                key1 =>
                    new Lazy<TValue>(() => factory(key1),
                    LazyThreadSafetyMode.ExecutionAndPublication)).Value;
    }
}

La mise en cache des objets XmlSerializer peut s'avérer particulièrement coûteuse, et le démarrage de l'application soulève également de nombreuses questions. Et il y a plus à cela: si ce sont des sérialiseurs personnalisés, il y aura aussi une fuite de mémoire pour le reste du cycle de vie du processus. Le seul avantage du ConcurrentDictionary dans ce cas est que pour le reste du cycle de vie du processus, il n'y aura pas de verrous, mais le démarrage de l'application et l'utilisation de la mémoire seraient inacceptables. Ceci est un travail pour notre ConcurrentDictionary, augmenté avec Lazy:

private ConcurrentDictionary<Type, Lazy<XmlSerializer>> _serializers =
    new ConcurrentDictionary<Type, Lazy<XmlSerializer>>();

public XmlSerializer GetSerialier(Type t)
{
    return _serializers.GetOrCreateLazy(t, BuildSerializer);
}

private XmlSerializer BuildSerializer(Type t)
{
    throw new NotImplementedException("and this is a homework");
}