ConcurrentDictionary does not seem to label elements for the GC when they are deleted

I was surprised to find that the memory capacity of my application continues to grow - the longer it runs, the more memory it consumes. So, with some magic, windbgI pointed out the problem to my little LRU based cache ConcurrentDictionary. The CD has many advantages that were very cool for me (one of which is that its data never ends in LOH). TryAddand TryRemove- two methods used to add and expose elements. !gcrootsome old element returns me to my cache. Some research with ILSpy led me to this conclusion:

TryRemovedoes not delete the item. All he does is change the pointers to the links of the list to skip to never assign the value of an array element null. This prevents the GC from collecting old evicted objects.

Really? Is this a known issue? If this is my only option TryUpdate(key, null), then TryRemove(key)? If so, I must block access to ConcurrentDictionary, which is oxymoronic.

Here is an ILSpy dump:

// System.Collections.Concurrent.ConcurrentDictionary<TKey, TValue>
private bool TryRemoveInternal(TKey key, out TValue value, bool matchValue, TValue oldValue)
{
    while (true)
    {
        ConcurrentDictionary<TKey, TValue>.Tables tables = this.m_tables;
        int num;
        int num2;
        this.GetBucketAndLockNo(this.m_comparer.GetHashCode(key), out num, out num2, tables.m_buckets.Length, tables.m_locks.Length);
        lock (tables.m_locks[num2])
        {
            if (tables != this.m_tables)
            {
                continue;
            }
            ConcurrentDictionary<TKey, TValue>.Node node = null;
            ConcurrentDictionary<TKey, TValue>.Node node2 = tables.m_buckets[num];
            while (node2 != null)
            {
                if (this.m_comparer.Equals(node2.m_key, key))
                {
                    bool result;
                    if (matchValue && !EqualityComparer<TValue>.Default.Equals(oldValue, node2.m_value))
                    {
                        value = default(TValue);
                        result = false;
                        return result;
                    }
                    if (node == null)
                    {
                        Volatile.Write<ConcurrentDictionary<TKey, TValue>.Node>(ref tables.m_buckets[num], node2.m_next);
                    }
                    else
                    {
                        node.m_next = node2.m_next;
                    }
                    value = node2.m_value;
                    tables.m_countPerLock[num2]--;
                    result = true;
                    return result;
                }
                else
                {
                    node = node2;
                    node2 = node2.m_next;
                }
            }
        }
        break;
    }
    value = default(TValue);
    return false;
}
+3
source share

All Articles