我有一个处理对象的库(碰巧是不可变的)。很多时候,它一遍又一遍地处理相同的对象,但它无法知道,所以它会做一堆重复的工作。
一个很好的类比可能是我正在渲染一个界面。我的渲染函数返回一个帧,但是渲染该帧的99%的工作被使用和丢弃。如果下次我被要求渲染包含该控件的帧时,大部分工作都可以重复使用,我认识到它是相同的控件(可能只是略有不同的状态)。
如果这是现实生活,我和人打交道,我会给每个人一张“身份证”,以便他们下次访问时给我回复,这样如果我看到的话,我可以在我的数据库中查找他们又来了。不幸的是,在大多数编程语言(包括C#)中,您不能仅使用自定义数据“增强”任意对象,以便以后更容易识别它们。
我考虑过保留以前见过的对象缓存:
Dictionary<Object, ReallyExpensiveInformation> cache = new ...;
或者,如果我们担心错误地覆盖“等于”的对象:
Dictionary<CachedObject, ReallyExpensiveInformation> cache = new ...;
// Where CachedObject is defined as:
private class CachedObject : IEquatable<CachedObject> {
Object Value { get; set; }
// Get the native hash code that is based on the object reference
override int GetHashCode() => RuntimeHelpers.GetHashCode(Value);
// Ensure each new reference is processed, even if they would claim to be equivalent
override bool Equals(CachedObject other) => ReferenceEquals(Value, other.Value);
}
这些方法在我的脑海中都遇到了同样的核心问题,即他们通过坚持可能已经最终确定的对象来创建内存泄漏。
我说我一遍又一遍地遇到相同的物体,但我遇到了许多我再也见不到的物体。我有足够的空间来存储ReallyExpensiveInformation
,因为最终结果很小,但Objects
可能很大,我不知道调用者持有什么,以及他们是什么正在放弃收集垃圾。调用者可能会或可能不会保留对这些对象的大量引用,但如果他们决定使用它们,我不希望我的缓存成为阻止它们被垃圾收集的东西。
我几乎希望有一些方法可以挂钩一个对象的“引用计数”,并在我最后一次保持时转储它。
无论如何,我的下一个想法是拥有自己的“GarbageCollection”阶段。使用上次访问时间增强我的字典,并清除一段时间内未重复使用的所有条目。这是一个需要解决的复杂问题。我想知道我是否完全忽略了一些简单的解决方案。
希望很明显我无法控制我正在处理的对象,否则我会增强它们的对象模型。
答案 0 :(得分:0)
所以,@ Eser刚刚转向System.WeakReference
。让我们看看它会是什么样子:
private class CachedReference : IEquatable<CachedObject> {
private readonly int _hash;
public WeakReference Value { get; }
public CachedReference (Object obj) {
Value = new WeakReference(obj);
_hash = RuntimeHelpers.GetHashCode(Value);
}
override int GetHashCode() => _hash;
// If our value is garbage collected, we'll stop matching anything
override bool Equals(CachedObject other) =>
Value != null && ReferenceEquals(Value, other.Value);
}
ConcurrentDictionary<CachedReference, ReallyExpensiveInformation> cache = new ...;
// And we use it like this
public ReallyExpensiveInformation GetOrCompute(Object obj) =>
return cache.GetOrAdd(new CachedReference(obj), key => ComputeExpensiveInfo(key.Value));
看起来我们仍然需要一些流程来收集&#34; Garbage Collect&#34;已悬空的字典条目:
或许这样的事情?
Timer garbageCollection = new Timer { AutoReset = true; Interval = 1000 };
Timer.Elapsed += () => cache.Keys.Where(k => k.Value == null)
.ToList().ForAll(k => cache.TryRemove(k, out _));
这有点严重,我不喜欢周期性 O(N)
删除干扰缓存的想法。 (即&#34;冻结&#34;或在清理或更换时减慢访问速度)。
绝对比我们好,但我想知道是否有更优雅的事情可以做清理。