जो जानकारी मैं यहां देता हूं वह नई नहीं है, मैंने इसे पूर्णता के लिए जोड़ा है।
इस कोड का विचार काफी सरल है:
- वस्तुओं को एक विशिष्ट आईडी की आवश्यकता होती है, जो डिफ़ॉल्ट रूप से नहीं होती है। इसके बजाय, हमें अगली सबसे अच्छी चीज़ पर भरोसा करना होगा, जो
RuntimeHelpers.GetHashCode
हमें एक विशिष्ट आईडी की तरह प्राप्त करना है
- विशिष्टता की जांच करने के लिए, इसका मतलब है कि हमें उपयोग करने की आवश्यकता है
object.ReferenceEquals
- हालाँकि, हम अभी भी एक यूनिक आईडी रखना चाहेंगे, इसलिए मैंने एक जोड़ा
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
एक दो बातें करता है। सबसे महत्वपूर्ण बात यह है कि यह कचरा संग्रहकर्ता की परवाह नहीं करता है, अर्थात: इस तालिका में जिन वस्तुओं का आप संदर्भ लेते हैं, उन्हें ध्यानपूर्वक एकत्रित किया जाएगा। यदि आप किसी ऑब्जेक्ट को देखते हैं, तो यह मूल रूप से ऊपर दिए गए शब्दकोश के समान है।
जिज्ञासु नहीं? आखिरकार, जब कोई वस्तु जीसी द्वारा एकत्र की जा रही है, तो यह जांचती है कि क्या वस्तु के संदर्भ हैं, और यदि हैं, तो यह उन्हें एकत्र करता है। तो अगर वहाँ से एक वस्तु है ConditionalWeakTable
, तो संदर्भित वस्तु को क्यों एकत्र किया जाएगा?
ConditionalWeakTable
एक छोटी सी चाल का उपयोग करता है, जो कुछ अन्य .NET संरचनाओं का भी उपयोग करता है: ऑब्जेक्ट के संदर्भ को संग्रहीत करने के बजाय, यह वास्तव में एक इंटपार्ट को संग्रहीत करता है। क्योंकि यह एक वास्तविक संदर्भ नहीं है, इसलिए वस्तु को एकत्र किया जा सकता है।
तो, इस बिंदु पर पता करने के लिए 2 समस्याएं हैं। सबसे पहले, वस्तुओं को ढेर पर स्थानांतरित किया जा सकता है, इसलिए हम IntPtr के रूप में क्या उपयोग करेंगे? और दूसरा, हम कैसे जानते हैं कि वस्तुओं का एक सक्रिय संदर्भ है?
- ऑब्जेक्ट को ढेर पर पिन किया जा सकता है, और इसके असली पॉइंटर को स्टोर किया जा सकता है। जब जीसी हटाने के लिए ऑब्जेक्ट को हिट करता है, तो यह इसे अनपिन करता है और इसे इकट्ठा करता है। हालांकि, इसका मतलब यह होगा कि हमें एक पिन किया हुआ संसाधन मिलेगा, जो एक अच्छा विचार नहीं है यदि आपके पास बहुत सारी वस्तुएं हैं (स्मृति विखंडन के मुद्दों के कारण)। यह शायद यह नहीं है कि यह कैसे काम करता है।
- जब GC एक ऑब्जेक्ट को स्थानांतरित करता है, तो वह वापस कॉल करता है, जो तब संदर्भों को अपडेट कर सकता है। यह हो सकता है कि बाहरी कॉल द्वारा इसे कैसे लागू किया जाए
DependentHandle
- लेकिन मेरा मानना है कि यह थोड़ा अधिक परिष्कृत है।
- ऑब्जेक्ट के लिए सूचक ही नहीं, बल्कि GC से सभी ऑब्जेक्ट की सूची में एक पॉइंटर संग्रहीत किया जाता है। इस सूची में IntPtr या तो एक सूचकांक या एक सूचक है। सूची केवल तब बदलती है जब कोई वस्तु पीढ़ियों को बदलती है, जिस बिंदु पर एक साधारण कॉलबैक बिंदुओं को अपडेट कर सकता है। यदि आपको याद है कि मार्क और स्वीप कैसे काम करता है, तो यह अधिक समझ में आता है। कोई पिनिंग नहीं है, और हटाना पहले जैसा है। मेरा मानना है कि यह कैसे काम करता है
DependentHandle
।
इस अंतिम समाधान के लिए आवश्यक है कि रनटाइम सूची बकेट का फिर से उपयोग न करे जब तक कि उन्हें स्पष्ट रूप से मुक्त नहीं किया जाता है, और इसके लिए यह भी आवश्यक है कि सभी ऑब्जेक्ट रनटाइम के लिए कॉल द्वारा पुनर्प्राप्त किए जाते हैं।
यदि हम मानते हैं कि वे इस समाधान का उपयोग करते हैं, तो हम दूसरी समस्या का समाधान भी कर सकते हैं। मार्क और स्वीप एल्गोरिथ्म का ट्रैक रखता है कि किन वस्तुओं को एकत्र किया गया है; जैसे ही इसे एकत्र किया गया है, हम इस बिंदु पर जानते हैं। एक बार ऑब्जेक्ट की जाँच करने के बाद यदि ऑब्जेक्ट वहाँ है, तो यह 'फ्री' कहता है, जो पॉइंटर और लिस्ट एंट्री को हटा देता है। वस्तु वास्तव में चली गई है।
इस बिंदु पर ध्यान देने वाली एक महत्वपूर्ण बात यह है कि यदि ConditionalWeakTable
कई थ्रेड में अपडेट किया गया है और यदि यह थ्रेड सुरक्षित नहीं है तो चीजें बहुत गलत हो जाती हैं। परिणाम एक स्मृति रिसाव होगा। यही कारण है कि सभी कॉल ConditionalWeakTable
एक सरल 'लॉक' करते हैं जो यह सुनिश्चित करता है कि ऐसा नहीं होता है।
एक और ध्यान देने वाली बात यह है कि प्रविष्टियों की सफाई एक बार में ही हो जाती है। जबकि वास्तविक वस्तुओं को जीसी द्वारा साफ किया जाएगा, प्रविष्टियां नहीं हैं। यही कारण है कि ConditionalWeakTable
केवल आकार में बढ़ता है। एक बार जब यह एक निश्चित सीमा से टकराता है (हैश में टकराव की संभावना से निर्धारित होता है), यह एक ट्रिगर करता है Resize
, जो यह जांचता है कि क्या वस्तुओं को साफ करना है - यदि वे करते हैं, free
तो जीसी प्रक्रिया में, 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
}