我应该在哪里找到使用弱引用的IDictionary
的良好实现?
Dictionary
应该仅保存对值的弱引用,并最终清理不可访问的引用。
还是我自己写比较好?
我应该在哪里找到使用弱引用的IDictionary
的良好实现?
Dictionary
应该仅保存对值的弱引用,并最终清理不可访问的引用。
还是我自己写比较好?
ConditionalWeakTable类使用弱键,只要在表格之外没有对键的其他引用存在,就会自动删除键/值条目。
ReferenceEquals
而不是GetHashCode
和Equals
来进行相等性检查。请查看https://dev59.com/gGoy5IYBdhLWcg3wnPOJ#8441180以获得更全面的讨论。 - larsmoaConditionalWeakTable
类是一个具有弱键的弱字典。因此,我认为这不是一个正确的答案。 - J D你需要自己编写代码。这个过程相对简单,实现 IDictionary<T,T>
接口,然后将实际值存储为 WeakReferences<T>
。你可以使用 TryGetTarget
在添加/选择时检查值是否仍然存在。
public class WeakDictionary <TKey,TValue> : IDictionary<TKey,TValue>
where TValue : class
{
private readonly Dictionary<TKey,WeakReference<TValue>> innerDictionary = new Dictionary<TKey,WeakReference<TValue>>();
public TValue Index[ TKey key ]
{
get
{
// Use .TryGetTarget instead of .IsAlive and .Target
if (this.innerDictionary.TryGetValue(key, out WeakReference<TValue> wf) && wf.TryGetTarget(out TValue value))
{
return value;
}
return null;
}
}
private void Cull()
{
var deadKeys = this.innerDictionary.Where(kvp => kvp.Value.IsAlive).Select(kvp => kvp.Key).ToList();
foreach (var key in deadKeys)
{
_ = this.innerDictionary.TryRemove(key);
}
}
}
reference.IsAlive
和 reference.Target
之间可能会发生垃圾回收,因此这种解决方案容易出现竞态条件。 - rr-ConditionalWeakTable
还没有出现。至于Finasposer
类,基本思想是封装终结逻辑在一个通用类中,当特定对象不存在时运行指定的代码。请注意,Finalize方法实际上并不表示对象何时被垃圾回收,而是当对象树中某处注册了Finalize
方法时,对象将会被垃圾回收。找出对象何时实际上被垃圾回收... - supercatConditionalWeakTable
实现了IEnumerable<...>
。 - Vlad这种方法可以避免其他解决方案的性能问题。
(不需要在每次请求时调用“收缩”方法来手动清除无用对象。而且这些“收缩”方法需要在每次调用时循环处理每个项目。虽然我有一个“收缩”方法,但只在枚举项目时才会调用。)
解决问题的关键是在ConditionalWeakTable中使用一个“持有者”对象作为值,这样当键被删除时,持有者的终结器会触发,将该键从“活动列表”中移除。
我进行了测试,它可以正常工作。
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Util
{
public class WeakDictionary<TKey, TValue> : IDictionary<TKey, TValue>, IDisposable
where TKey : class
where TValue : class
{
private readonly object locker = new object();
//private readonly HashSet<WeakReference> weakKeySet = new HashSet<WeakReference>(new ObjectReferenceEqualityComparer<WeakReference>());
private ConditionalWeakTable<TKey, WeakKeyHolder> keyHolderMap = new ConditionalWeakTable<TKey, WeakKeyHolder>();
private Dictionary<WeakReference, TValue> valueMap = new Dictionary<WeakReference, TValue>(new ObjectReferenceEqualityComparer<WeakReference>());
private class WeakKeyHolder
{
private WeakDictionary<TKey, TValue> outer;
private WeakReference keyRef;
public WeakKeyHolder(WeakDictionary<TKey, TValue> outer, TKey key)
{
this.outer = outer;
this.WeakRef = new WeakReference(key);
}
public WeakReference WeakRef { get; private set; }
~WeakKeyHolder()
{
this.outer?.onKeyDrop(this.WeakRef); // Nullable operator used just in case this.outer gets set to null by GC before this finalizer runs. But I haven't had this happen.
}
}
private void onKeyDrop(WeakReference weakKeyRef)
{
lock(this.locker)
{
if (!this.bAlive)
return;
//this.weakKeySet.Remove(weakKeyRef);
this.valueMap.Remove(weakKeyRef);
}
}
// The reason for this is in case (for some reason which I have never seen) the finalizer trigger doesn't work
// There is not much performance penalty with this, since this is only called in cases when we would be enumerating the inner collections anyway.
private void manualShrink()
{
var keysToRemove = this.valueMap.Keys.Where(k => !k.IsAlive).ToList();
foreach (var key in keysToRemove)
valueMap.Remove(key);
}
private Dictionary<TKey, TValue> currentDictionary
{
get
{
lock(this.locker)
{
this.manualShrink();
return this.valueMap.ToDictionary(p => (TKey) p.Key.Target, p => p.Value);
}
}
}
public TValue this[TKey key]
{
get
{
if (this.TryGetValue(key, out var val))
return val;
throw new KeyNotFoundException();
}
set
{
this.set(key, value, isUpdateOkay: true);
}
}
private bool set(TKey key, TValue val, bool isUpdateOkay)
{
lock (this.locker)
{
if (this.keyHolderMap.TryGetValue(key, out var weakKeyHolder))
{
if (!isUpdateOkay)
return false;
this.valueMap[weakKeyHolder.WeakRef] = val;
return true;
}
weakKeyHolder = new WeakKeyHolder(this, key);
this.keyHolderMap.Add(key, weakKeyHolder);
//this.weakKeySet.Add(weakKeyHolder.WeakRef);
this.valueMap.Add(weakKeyHolder.WeakRef, val);
return true;
}
}
public ICollection<TKey> Keys
{
get
{
lock(this.locker)
{
this.manualShrink();
return this.valueMap.Keys.Select(k => (TKey) k.Target).ToList();
}
}
}
public ICollection<TValue> Values
{
get
{
lock (this.locker)
{
this.manualShrink();
return this.valueMap.Select(p => p.Value).ToList();
}
}
}
public int Count
{
get
{
lock (this.locker)
{
this.manualShrink();
return this.valueMap.Count;
}
}
}
public bool IsReadOnly => false;
public void Add(TKey key, TValue value)
{
if (!this.set(key, value, isUpdateOkay: false))
throw new ArgumentException("Key already exists");
}
public void Add(KeyValuePair<TKey, TValue> item)
{
this.Add(item.Key, item.Value);
}
public void Clear()
{
lock(this.locker)
{
this.keyHolderMap = new ConditionalWeakTable<TKey, WeakKeyHolder>();
this.valueMap.Clear();
}
}
public bool Contains(KeyValuePair<TKey, TValue> item)
{
WeakKeyHolder weakKeyHolder = null;
object curVal = null;
lock (this.locker)
{
if (!this.keyHolderMap.TryGetValue(item.Key, out weakKeyHolder))
return false;
curVal = weakKeyHolder.WeakRef.Target;
}
return (curVal?.Equals(item.Value) == true);
}
public bool ContainsKey(TKey key)
{
lock (this.locker)
{
return this.keyHolderMap.TryGetValue(key, out var weakKeyHolder);
}
}
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
((IDictionary<TKey, TValue>) this.currentDictionary).CopyTo(array, arrayIndex);
}
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
return this.currentDictionary.GetEnumerator();
}
public bool Remove(TKey key)
{
lock (this.locker)
{
if (!this.keyHolderMap.TryGetValue(key, out var weakKeyHolder))
return false;
this.keyHolderMap.Remove(key);
this.valueMap.Remove(weakKeyHolder.WeakRef);
return true;
}
}
public bool Remove(KeyValuePair<TKey, TValue> item)
{
lock (this.locker)
{
if (!this.keyHolderMap.TryGetValue(item.Key, out var weakKeyHolder))
return false;
if (weakKeyHolder.WeakRef.Target?.Equals(item.Value) != true)
return false;
this.keyHolderMap.Remove(item.Key);
this.valueMap.Remove(weakKeyHolder.WeakRef);
return true;
}
}
public bool TryGetValue(TKey key, out TValue value)
{
lock (this.locker)
{
if (!this.keyHolderMap.TryGetValue(key, out var weakKeyHolder))
{
value = default(TValue);
return false;
}
value = this.valueMap[weakKeyHolder.WeakRef];
return true;
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
private bool bAlive = true;
public void Dispose()
{
this.Dispose(true);
}
protected void Dispose(bool bManual)
{
if (bManual)
{
Monitor.Enter(this.locker);
if (!this.bAlive)
return;
}
try
{
this.keyHolderMap = null;
this.valueMap = null;
this.bAlive = false;
}
finally
{
if (bManual)
Monitor.Exit(this.locker);
}
}
~WeakDictionary()
{
this.Dispose(false);
}
}
public class ObjectReferenceEqualityComparer<T> : IEqualityComparer<T>
{
public static ObjectReferenceEqualityComparer<T> Default = new ObjectReferenceEqualityComparer<T>();
public bool Equals(T x, T y)
{
return ReferenceEquals(x, y);
}
public int GetHashCode(T obj)
{
return RuntimeHelpers.GetHashCode(obj);
}
}
public class ObjectReferenceEqualityComparer : ObjectReferenceEqualityComparer<object>
{
}
}
ConditionalWeakTable
类保持对其键的弱引用,但我们希望弱引用在值上。因此,我们使用我们字典的值作为ConditionalWeakTable
的键。ConditionalWeakTable
会删除相关条目,这也导致value被垃圾收集(假设没有对该值的其他引用)。这会导致值对象的解构器被调用。我们利用这一点,以便立即从内部字典中删除相应的条目。public class WeakDictionary<TKey, TValue> : IDictionary<TKey, TValue>
where TValue : class
{
private readonly Dictionary<TKey, WeakReference> internalDictionary = new Dictionary<TKey, WeakReference>();
private readonly ConditionalWeakTable<TValue, Finalizer> conditionalWeakTable = new ConditionalWeakTable<TValue, Finalizer>();
public TValue this[TKey key]
{
get => (TValue)internalDictionary[key].Target;
set
{
Remove(key);
Add(key, value);
}
}
public ICollection<TKey> Keys => internalDictionary.Keys;
public ICollection<TValue> Values => internalDictionary.Values.Select(r => (TValue)r.Target).ToArray();
public int Count => internalDictionary.Count;
public bool IsReadOnly => false;
public void Add(TKey key, TValue value)
{
internalDictionary.Add(key, new WeakReference(value));
var finalizer = new Finalizer(key);
finalizer.ValueFinalized += k => Remove(k);
conditionalWeakTable.Add(value, finalizer);
}
public void Add(KeyValuePair<TKey, TValue> item) => Add(item.Key, item.Value);
// Implement the remaining IDictionary<,> methods to simply relay the method call to internalDictionary.
// See https://github.com/bhaeussermann/weak-dictionary/blob/main/src/WeakDictionary/WeakDictionary.cs for the complete implementation.
// ...
private sealed class Finalizer
{
private readonly TKey valueKey;
public Finalizer(TKey valueKey)
{
this.valueKey = valueKey;
}
~Finalizer()
{
ValueFinalized?.Invoke(valueKey);
}
public event ValueFinalizedDelegate ValueFinalized;
}
private delegate void ValueFinalizedDelegate(TKey valueKey);
}
[Test]
public void WeakDictionary()
{
var v1 = new ValueType();
var v2 = new ValueType();
var v3 = new ValueType();
var dictionary = new WeakDictionary<int, ValueType>
{
{ 1, v1 },
{ 2, v2 },
{ 3, v3 }
};
var weakReference = new WeakReference(v2);
v2 = null;
// Loop forces non-referenced values to be garbage collected on .NET Core (see https://stackoverflow.com/a/68836653/359765)
for (int i = 0; i < 1; i++)
{
GC.Collect();
}
Assert.IsFalse(weakReference.IsAlive);
CollectionAssert.AreEquivalent(new int[] { 1, 3 }, dictionary.Keys, "Unexpected keys after garbage collection.");
// These references to v1 and v2 prevent the compiler from adding optimizations that will cause v1 and v2 to be garbage collected.
v1.ToString();
v3.ToString();
}
private class ValueType { }
这是我实现的一个并发弱(值)字典版本:
public class WeakConcurrentDictionary<TKey, TValue> : IDictionary<TKey, TValue> where TValue : class
{
private readonly ConcurrentDictionary<TKey, WeakReference<TValue>> _internalDictionary =
new ConcurrentDictionary<TKey, WeakReference<TValue>>();
public TValue this[TKey key]
{
get
{
if (_internalDictionary.TryGetValue(key, out var weakReference) &&
weakReference.TryGetTarget(out var value))
return value;
return null;
}
set
{
_internalDictionary.TryAdd(key, new WeakReference<TValue>(value));
}
}
public ICollection<TKey> Keys => _internalDictionary.Keys;
public ICollection<TValue> Values => _internalDictionary.Values
.Select(_ => _.GetTarget())
.Where(_ => _ != null)
.ToList();
public int Count => _internalDictionary.Count;
public bool IsReadOnly => false;
public void Add(TKey key, TValue value)
{
Purge();
if (!_internalDictionary.TryAdd(key, new WeakReference<TValue>(value)))
{
throw new InvalidOperationException("Key already existing");
}
}
public void Add(KeyValuePair<TKey, TValue> item)
{
throw new NotSupportedException();
}
public void Clear()
{
_internalDictionary.Clear();
}
public bool Contains(KeyValuePair<TKey, TValue> item) => _internalDictionary.TryGetValue(item.Key, out var weakReference) &&
weakReference.GetTarget() == item.Value;
public bool ContainsKey(TKey key) => _internalDictionary.TryGetValue(key, out var weakReference) &&
weakReference.IsAlive();
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
Purge();
_internalDictionary
.Select(_ => new KeyValuePair<TKey, TValue>(_.Key, _.Value.GetTarget()))
.Where(_ => _.Value != null)
.ToList()
.CopyTo(array, arrayIndex);
}
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
Purge();
return _internalDictionary
.Select(_ => new KeyValuePair<TKey, TValue>(_.Key, _.Value.GetTarget()))
.Where(_ => _.Value != null)
.GetEnumerator();
}
public bool Remove(TKey key)
{
return _internalDictionary.TryRemove(key, out var weakReference);
}
public bool Remove(KeyValuePair<TKey, TValue> item)
{
throw new NotSupportedException();
}
public bool TryGetValue(TKey key, out TValue value)
{
value = null;
if (_internalDictionary.TryGetValue(key, out var weakReference))
{
value = weakReference.GetTarget();
}
return value != null;
}
IEnumerator IEnumerable.GetEnumerator()
{
Purge();
return GetEnumerator();
}
public void Purge()
{
foreach (var itemToRemove in _internalDictionary
.Select(_ => new KeyValuePair<TKey, TValue>(_.Key, _.Value.GetTarget()))
.Where(_ => _.Value == null))
{
_internalDictionary.TryRemove(itemToRemove.Key, out var weakReference);
}
}
}
public static class WeakReferenceExtensions
{
public static bool IsAlive<T>([NotNull] this WeakReference<T> weakReference) where T : class =>
weakReference.TryGetTarget(out var target);
public static T GetTarget<T>([NotNull] this WeakReference<T> weakReference, T defaultValue = default(T)) where T : class
{
if (!weakReference.TryGetTarget(out T target))
return defaultValue;
return target;
}
}
还有一个测试证明引用值实际上被丢弃了:
[TestMethod]
public void TestWeakDictionary()
{
var weakDict = new WeakConcurrentDictionary<string, TestItem>();
{
var testItem = new TestItem();
weakDict.Add("testitem", testItem);
Assert.AreEqual(1, weakDict.Count);
Assert.AreSame(testItem, weakDict["testitem"]);
}
GC.Collect();
Assert.IsNull(weakDict["testitem"]);
weakDict.Purge();
Assert.AreEqual(0, weakDict.Count);
}
一些注意事项:
拥有对值的弱引用是一回事,但我发现字典键也可能是内存泄漏的源头。这是一个仅包含对键的弱引用的基本实现:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Common.library.collections {
/// <summary>
/// THIS DICTIONARY WILL NOT "HANG ON" TO THE KEYS IT USES
/// IF THE KEY IS GARBAGE COLLECTED, THE VALUE WILL BE RELEASED TOO
/// </summary>
public class Dictionary_usingWeakKey<K, V> {
//MAP FROM HASH CODE TO LIST OF KEY/VALUE PAIRS
private Dictionary<int, List<Pair>> dic = new Dictionary<int, List<Pair>>();
public void Add(K key, V value) {
if (value==null){
this.Remove(key);
return;
}//endif
List<Pair> list = null;
dic.TryGetValue(key.GetHashCode(), out list);
if (list == null) {
list = new List<Pair>();
dic.Add(key.GetHashCode(), list);
}//endif
Boolean isDirty = false;
foreach(Pair p in list){
if (p.Key.Target == null) {
isDirty = true;
continue;
}//endif
if (p.Key.Target == (Object)key) {
p.Value = (Object)value;
if (isDirty) cleanList(list);
return;
}//endif
}//for
if (isDirty) cleanList(list);
Pair newP=new Pair();
newP.Key = new WeakReference(key);
newP.Value = value;
list.Add(newP);
}//method
public bool ContainsKey(K key) {
List<Pair> list = null;
dic.TryGetValue(key.GetHashCode(), out list);
if (list == null) return false;
Boolean isDirty = false;
foreach (Pair p in list) {
if (p.Key.Target == null) {
isDirty = true;
continue;
}//endif
if (p.Key.Target == (Object)key) {
if (isDirty) cleanList(list);
return true;
}//endif
}//for
if (isDirty) cleanList(list);
return false;
}//method
private void cleanList(List<Pair> list) {
var temp = (from Pair p in list where p.Key.Target != null select p);
list.Clear();
list.AddRange(temp);
}//method
public bool Remove(K key) {
List<Pair> list = null;
dic.TryGetValue(key.GetHashCode(), out list);
if (list == null) return true;
foreach (Pair p in list) {
if (p.Key.Target == (Object)key) {
p.Value = null;
break;
}//endif
}//for
cleanList(list);
return true;
}//method
public V this[K key] {
get {
List<Pair> list = null;
dic.TryGetValue(key.GetHashCode(), out list);
if (list == null) return default(V);
Boolean isDirty = false;
foreach (Pair p in list) {
if (p.Key.Target == null) {
isDirty = true;
continue;
}//endif
if (p.Key.Target == (Object)key) {
if (isDirty) cleanList(list);
return (V)p.Value;
}//endif
}//for
if (isDirty) cleanList(list);
return default(V);
}
set {
this.Add(key, value);
}
}
public void Add(KeyValuePair<K, V> item) {
throw new NotImplementedException();
}
public void Clear() {
dic.Clear();
}
public bool Contains(KeyValuePair<K, V> item) {
throw new NotImplementedException();
}
public void CopyTo(KeyValuePair<K, V>[] array, int arrayIndex) {
throw new NotImplementedException();
}
public int Count {
get {
throw new NotImplementedException();
//return dic.Count();
}
}
public bool IsReadOnly {
get { return false; }
}
public bool Remove(KeyValuePair<K, V> item) {
throw new NotImplementedException();
}
public IEnumerator<KeyValuePair<K, V>> GetEnumerator() {
throw new NotImplementedException();
//return dic.GetEnumerator();
}
//System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
// return ((System.Collections.IEnumerable)dic).GetEnumerator();
//}
}//class
public class Pair{
public WeakReference Key;
public Object Value;
}//method
}
IDictionary
,但ConditionalWeakTable正是我在Google搜索时真正寻找的东西。感谢这个答案。 - default.kramer