Burada verdiğim bilgiler yeni değil, bunu tamlık için ekledim.
Bu kodun fikri oldukça basit:
- Nesnelerin, varsayılan olarak orada olmayan benzersiz bir kimliğe ihtiyacı vardır. Bunun yerine, bir sonraki en iyi şeye güvenmeliyiz, bu da
RuntimeHelpers.GetHashCode
bize bir tür benzersiz kimlik
- Benzersizliği kontrol etmek için bu, kullanmamız gerektiği anlamına gelir
object.ReferenceEquals
- Bununla birlikte, yine de benzersiz bir kimliğimiz olmasını istiyoruz, bu yüzden
GUID
tanım gereği benzersiz olan bir ekledim .
- Çünkü mecbur kalmazsam her şeyi kilitlemeyi sevmiyorum, kullanmıyorum
ConditionalWeakTable
.
Bu size aşağıdaki kodu verecektir:
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;
}
}
Bunu kullanmak UniqueIdMapper
için, nesnelerin bir örneğini oluşturun ve nesneler için döndürdüğü GUID'leri kullanın.
ek
Yani burada biraz daha fazlası var; hakkında biraz yazmama izin verin ConditionalWeakTable
.
ConditionalWeakTable
birkaç şey yapar. En önemli şey çöp toplayıcıyı umursamaması, yani bu tabloda referans verdiğiniz nesneler ne olursa olsun toplanacak. Bir nesneye bakarsanız, temelde yukarıdaki sözlükle aynı şekilde çalışır.
Meraklı hayır mı? Sonuçta, bir nesne GC tarafından toplanırken, nesneye referans olup olmadığını kontrol eder ve varsa onları toplar. Öyleyse, öğesinden bir nesne varsa ConditionalWeakTable
, o zaman neden başvurulan nesne toplanacak?
ConditionalWeakTable
diğer bazı .NET yapılarının da kullandığı küçük bir numara kullanır: nesneye bir başvuru depolamak yerine, aslında bir IntPtr depolar. Bu gerçek bir referans olmadığı için nesne toplanabilir.
Yani, bu noktada ele alınması gereken 2 sorun var. İlk olarak, nesneler yığın üzerinde hareket ettirilebilir, öyleyse IntPtr olarak ne kullanacağız? İkincisi, nesnelerin aktif bir referansı olduğunu nasıl bilebiliriz?
- Nesne öbek üzerine sabitlenebilir ve gerçek işaretçisi saklanabilir. GC, kaldırılmak üzere nesneye çarptığında, onu çözer ve toplar. Ancak bu, sabitlenmiş bir kaynak elde ettiğimiz anlamına gelir; bu, çok fazla nesneniz varsa (bellek parçalanma sorunları nedeniyle) iyi bir fikir değildir. Muhtemelen böyle çalışmıyor.
- GC bir nesneyi taşıdığında, geri çağırır ve bu daha sonra referansları güncelleyebilir. Dışarıdan gelen aramalar tarafından değerlendirilerek bu şekilde uygulanıyor olabilir
DependentHandle
- ancak biraz daha karmaşık olduğuna inanıyorum.
- Nesnenin kendisine işaretçi değil, ancak GC'deki tüm nesnelerin listesinde bir işaretçi saklanır. IntPtr, bu listedeki bir dizin veya bir göstericidir. Liste yalnızca bir nesne nesilleri değiştirdiğinde değişir, bu noktada basit bir geri arama işaretçileri güncelleyebilir. Mark & Sweep'in nasıl çalıştığını hatırlarsanız, bu daha mantıklıdır. Sabitleme yok ve sökme işlemi eskisi gibi. Bunun nasıl çalıştığına inanıyorum
DependentHandle
.
Bu son çözüm, çalışma zamanının liste paketlerini açıkça serbest bırakılıncaya kadar yeniden kullanmamasını ve ayrıca tüm nesnelerin çalışma zamanına yapılan bir çağrı ile alınmasını gerektirir.
Bu çözümü kullandıklarını varsayarsak, ikinci sorunu da ele alabiliriz. Mark & Sweep algoritması hangi nesnelerin toplandığını takip eder; toplanır toplanmaz, bu noktada biliyoruz. Nesne, nesnenin orada olup olmadığını kontrol ettikten sonra, işaretçiyi ve liste girişini kaldıran 'Ücretsiz'i çağırır. Nesne gerçekten gitti.
Bu noktada dikkat edilmesi gereken önemli bir nokta, ConditionalWeakTable
birden çok iş parçacığında güncellenirse ve iş parçacığı güvenli değilse , işlerin korkunç şekilde yanlış gitmesidir . Sonuç, bir bellek sızıntısı olur. Bu nedenle tüm aramalar, ConditionalWeakTable
bunun olmamasını sağlayan basit bir 'kilit' yapar.
Dikkat edilmesi gereken bir diğer husus da, girişleri temizlemenin arada bir yapılması gerektiğidir. Gerçek nesneler GC tarafından temizlenecek, ancak girişler temizlenmemektedir. Bu yüzden ConditionalWeakTable
sadece boyut olarak büyüyor. Belli bir limite ulaştığında (karmadaki çarpışma şansı ile belirlenir), Resize
nesnelerin temizlenmesinin gerekip gerekmediğini kontrol eden bir a tetikler - eğer varsa free
, IntPtr
tutamacı kaldırarak GC sürecinde çağrılır .
Bunun da neden DependentHandle
doğrudan açığa çıkmadığına inanıyorum - bir şeylerle uğraşmak ve sonuç olarak bir bellek sızıntısı yaşamak istemezsiniz. Bunun için bir sonraki en iyi şey a'dır (bir nesne yerine WeakReference
bir de depolar IntPtr
) - ancak maalesef 'bağımlılık' özelliğini içermiyor.
Geriye kalan şey, mekanikle oynamanız, böylece bağımlılığı eylemde görebilmenizdir. Birden çok kez başladığınızdan ve sonuçları izlediğinizden emin olun:
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
}