여기에 제공하는 정보는 새로운 것이 아니라 완전성을 위해 추가했습니다.
이 코드의 아이디어는 매우 간단합니다.
- 개체에는 기본적으로 존재하지 않는 고유 ID가 필요합니다. 대신, 우리는 차선책에 의존해야
RuntimeHelpers.GetHashCode
합니다. 일종의 고유 ID를 얻는 것입니다.
- 고유성을 확인하려면 다음을 사용해야 함을 의미합니다.
object.ReferenceEquals
- 그러나 우리는 여전히 고유 한 ID를 갖고 싶기 때문에
GUID
정의상 고유 한을 추가했습니다 .
- 필요하지 않은 경우 모든 것을 잠그는 것을 좋아하지 않기 때문에
ConditionalWeakTable
.
결합하면 다음 코드가 제공됩니다.
public class UniqueIdMapper
{
private class ObjectEqualityComparer : IEqualityComparer<object>
{
public bool Equals(object x, object y)
{
return object.ReferenceEquals(x, y);
}
public int GetHashCode(object obj)
{
return RuntimeHelpers.GetHashCode(obj);
}
}
private Dictionary<object, Guid> dict = new Dictionary<object, Guid>(new ObjectEqualityComparer());
public Guid GetUniqueId(object o)
{
Guid id;
if (!dict.TryGetValue(o, out id))
{
id = Guid.NewGuid();
dict.Add(o, id);
}
return id;
}
}
이를 사용하려면의 인스턴스를 만들고 UniqueIdMapper
개체에 대해 반환하는 GUID를 사용합니다.
추가
그래서, 여기에서 조금 더 진행되고 있습니다. 에 대해 조금 적어 보겠습니다 ConditionalWeakTable
.
ConditionalWeakTable
몇 가지를합니다. 가장 중요한 것은 가비지 수집기를 신경 쓰지 않는다는 것입니다. 즉,이 테이블에서 참조하는 개체는 상관없이 수집됩니다. 객체를 조회하면 기본적으로 위의 사전과 동일하게 작동합니다.
궁금하지 않습니까? 결국 GC에서 객체를 수집 할 때 객체에 대한 참조가 있는지 확인하고있는 경우이를 수집합니다. 그렇다면에서 개체 ConditionalWeakTable
가있는 경우 참조 된 개체가 수집되는 이유는 무엇입니까?
ConditionalWeakTable
일부 다른 .NET 구조도 사용하는 작은 트릭을 사용합니다. 개체에 대한 참조를 저장하는 대신 실제로 IntPtr을 저장합니다. 실제 참조가 아니기 때문에 개체를 수집 할 수 있습니다.
따라서이 시점에서 해결해야 할 두 가지 문제가 있습니다. 첫째, 객체는 힙에서 이동할 수 있으므로 IntPtr로 무엇을 사용할까요? 둘째, 객체에 활성 참조가 있는지 어떻게 알 수 있습니까?
- 개체를 힙에 고정 할 수 있으며 실제 포인터를 저장할 수 있습니다. GC가 제거를 위해 개체에 닿으면 고정을 해제하고 수집합니다. 그러나 이는 고정 된 리소스를 가져옴을 의미하며, 이는 메모리 조각화 문제로 인해 많은 개체가있는 경우에는 좋지 않습니다. 이것은 아마도 그것이 작동하는 방식이 아닐 것입니다.
- GC가 개체를 이동하면 다시 호출하여 참조를 업데이트 할 수 있습니다. 이것은 외부 호출로 판단하여 구현 된 방법 일 수
DependentHandle
있지만 약간 더 정교하다고 생각합니다.
- 개체 자체에 대한 포인터가 아니라 GC의 모든 개체 목록에있는 포인터가 저장됩니다. IntPtr은이 목록의 인덱스 또는 포인터입니다. 목록은 객체가 세대를 변경할 때만 변경되며,이 시점에서 간단한 콜백이 포인터를 업데이트 할 수 있습니다. Mark & Sweep의 작동 방식을 기억한다면이 방법이 더 의미가 있습니다. 고정이 없으며 제거는 이전과 동일합니다. 나는 이것이에서 작동하는 방식이라고 믿습니다
DependentHandle
.
이 마지막 솔루션은 명시 적으로 해제 될 때까지 런타임이 목록 버킷을 재사용하지 않도록 요구하며 런타임 호출을 통해 모든 객체를 검색해야합니다.
이 솔루션을 사용한다고 가정하면 두 번째 문제도 해결할 수 있습니다. Mark & Sweep 알고리즘은 수집 된 개체를 추적합니다. 수집 되 자마자이 시점에서 알 수 있습니다. 개체가 개체가 있는지 확인하면 'Free'를 호출하여 포인터와 목록 항목을 제거합니다. 개체가 정말 사라졌습니다.
이 시점에서 주목해야 할 한 가지 중요한 점은 ConditionalWeakTable
여러 스레드에서 업데이트되고 스레드로부터 안전하지 않으면 상황이 끔찍하게 잘못된다는 것입니다 . 그 결과 메모리 누수가 발생합니다. 이것이 모든 호출 ConditionalWeakTable
이 이것이 발생하지 않도록하는 간단한 '잠금'을 수행하는 이유 입니다.
주의해야 할 또 다른 사항은 항목 정리가 가끔씩 발생해야한다는 것입니다. 실제 개체는 GC에 의해 정리되지만 항목은 정리되지 않습니다. 이것이 ConditionalWeakTable
크기가 커지는 이유 입니다. 이 (해시에서 충돌 확률에 의해 결정) 특정 제한에 도달하면, 그것은 트리거 Resize
그들이 할 경우, - 개체를 정리해야하는 경우 어떤 검사, free
, 제거, GC의 과정에서 호출 IntPtr
핸들을.
나는 이것이 또한 DependentHandle
직접적으로 노출되지 않는 이유라고 생각합니다 -당신은 일을 엉망으로 만들고 그 결과 메모리 누수를 얻고 싶지 않습니다. 이에 대한 차선책은 WeakReference
( IntPtr
객체 대신을 저장하는 )이지만 불행히도 '종속성'측면을 포함하지 않습니다.
남은 것은 당신이 작동하는 의존성을 볼 수 있도록 역학을 가지고 놀아주는 것입니다. 여러 번 시작하고 결과를 확인하십시오.
class DependentObject
{
public class MyKey : IDisposable
{
public MyKey(bool iskey)
{
this.iskey = iskey;
}
private bool disposed = false;
private bool iskey;
public void Dispose()
{
if (!disposed)
{
disposed = true;
Console.WriteLine("Cleanup {0}", iskey);
}
}
~MyKey()
{
Dispose();
}
}
static void Main(string[] args)
{
var dep = new MyKey(true); // also try passing this to cwt.Add
ConditionalWeakTable<MyKey, MyKey> cwt = new ConditionalWeakTable<MyKey, MyKey>();
cwt.Add(new MyKey(true), dep); // try doing this 5 times f.ex.
GC.Collect(GC.MaxGeneration);
GC.WaitForFullGCComplete();
Console.WriteLine("Wait");
Console.ReadLine(); // Put a breakpoint here and inspect cwt to see that the IntPtr is still there
}