여기에 제공하는 정보는 새로운 것이 아니라 완전성을 위해 추가했습니다.
이 코드의 아이디어는 매우 간단합니다.
- 개체에는 기본적으로 존재하지 않는 고유 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
}