ข้อมูลที่ฉันให้ที่นี่ไม่ใช่เรื่องใหม่ฉันเพิ่งเพิ่มข้อมูลนี้เพื่อความสมบูรณ์
แนวคิดของรหัสนี้ค่อนข้างง่าย:
- ออบเจ็กต์ต้องมี ID เฉพาะซึ่งไม่มีโดยค่าเริ่มต้น แต่เราต้องพึ่งพาสิ่งที่ดีที่สุดถัดไปนั่นคือ
RuntimeHelpers.GetHashCode
การจัดเรียง ID เฉพาะให้เรา
- เพื่อตรวจสอบความเป็นเอกลักษณ์นี่หมายความว่าเราจำเป็นต้องใช้
object.ReferenceEquals
- อย่างไรก็ตามเรายังต้องการมี ID ที่ไม่ซ้ำกันดังนั้นฉันจึงเพิ่ม a
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 มันจะตรวจสอบว่ามีการอ้างอิงถึงวัตถุหรือไม่และหากมีก็จะรวบรวมวัตถุเหล่านั้น ดังนั้นหากมีวัตถุจาก the ConditionalWeakTable
ทำไมวัตถุที่อ้างอิงจะถูกรวบรวม?
ConditionalWeakTable
ใช้เคล็ดลับเล็ก ๆ ซึ่งโครงสร้าง. NET อื่น ๆ ก็ใช้เช่นกัน: แทนที่จะเก็บข้อมูลอ้างอิงไปยังวัตถุ แต่จะจัดเก็บ IntPtr เนื่องจากไม่ใช่ข้อมูลอ้างอิงจริงจึงสามารถรวบรวมวัตถุได้
ดังนั้น ณ จุดนี้มี 2 ปัญหาที่ต้องแก้ไข อย่างแรกวัตถุสามารถเคลื่อนย้ายบนฮีปได้แล้วเราจะใช้อะไรเป็น IntPtr? และประการที่สองเราจะรู้ได้อย่างไรว่าวัตถุมีการอ้างอิงที่ใช้งานอยู่?
- สามารถตรึงวัตถุไว้บนฮีปและสามารถจัดเก็บตัวชี้ที่แท้จริงได้ เมื่อ GC กระทบกับวัตถุเพื่อลบออกมันจะยกเลิกการตรึงและรวบรวมมัน อย่างไรก็ตามนั่นหมายความว่าเราได้รับทรัพยากรที่ตรึงไว้ซึ่งไม่ใช่ความคิดที่ดีหากคุณมีวัตถุจำนวนมาก (เนื่องจากปัญหาการกระจายตัวของหน่วยความจำ) นี่อาจไม่ใช่วิธีการทำงาน
- เมื่อ GC ย้ายวัตถุมันจะเรียกกลับซึ่งจะอัปเดตข้อมูลอ้างอิงได้ นี่อาจเป็นวิธีการใช้งานโดยตัดสินโดยการโทรภายนอกเข้า
DependentHandle
- แต่ฉันเชื่อว่ามันซับซ้อนกว่าเล็กน้อย
- ไม่ใช่ตัวชี้ไปที่วัตถุ แต่ตัวชี้ในรายการวัตถุทั้งหมดจาก GC จะถูกเก็บไว้ IntPtr เป็นดัชนีหรือตัวชี้ในรายการนี้ รายการจะเปลี่ยนแปลงเฉพาะเมื่อออบเจ็กต์เปลี่ยนรุ่นซึ่งเมื่อถึงจุดนั้นการเรียกกลับอย่างง่ายสามารถอัปเดตตัวชี้ หากคุณจำวิธีการทำงานของ Mark & Sweep สิ่งนี้จะสมเหตุสมผลกว่า ไม่มีการตรึงและการลบก็เหมือนเดิม ฉันเชื่อว่านี่เป็นวิธีการทำงานใน
DependentHandle
.
วิธีแก้ปัญหาสุดท้ายนี้ต้องการให้รันไทม์ไม่ใช้ที่เก็บรายการซ้ำจนกว่าจะได้รับการปลดปล่อยอย่างชัดเจนและยังต้องการให้อ็อบเจ็กต์ทั้งหมดถูกเรียกใช้โดยการเรียกไปยังรันไทม์
หากเราคิดว่าพวกเขาใช้วิธีนี้เราสามารถแก้ไขปัญหาที่สองได้ อัลกอริทึม Mark & Sweep จะติดตามว่ามีการรวบรวมวัตถุใดบ้าง ทันทีที่รวบรวมเรารู้ ณ จุดนี้ เมื่อวัตถุตรวจสอบว่ามีวัตถุอยู่หรือไม่มันจะเรียก 'ฟรี' ซึ่งจะลบตัวชี้และรายการออก วัตถุหายไปจริงๆ
สิ่งสำคัญอย่างหนึ่งที่ควรทราบในตอนนี้ก็คือสิ่งที่ผิดพลาดอย่างมากหากConditionalWeakTable
มีการอัปเดตในหลายเธรดและหากเธรดไม่ปลอดภัย ผลที่ตามมาคือหน่วยความจำรั่ว นี่คือเหตุผลที่ทุกสายเข้าConditionalWeakTable
ทำ 'ล็อค' แบบธรรมดาซึ่งทำให้มั่นใจได้ว่าสิ่งนี้จะไม่เกิดขึ้น
สิ่งที่ควรทราบอีกประการหนึ่งคือการล้างรายการจะต้องเกิดขึ้นนาน ๆ ครั้ง ในขณะที่วัตถุจริงจะถูกล้างโดย GC แต่รายการจะไม่ ด้วยเหตุนี้จึงConditionalWeakTable
มีขนาดโตขึ้นเท่านั้น เมื่อถึงขีด จำกัด ที่กำหนด (กำหนดโดยโอกาสในการชนกันในแฮช) มันจะเรียกใช้ a Resize
ซึ่งตรวจสอบว่าต้องล้างวัตถุหรือไม่ - ถ้าเป็นเช่นนั้นfree
จะถูกเรียกในกระบวนการ GC โดยถอดที่IntPtr
จับออก
ฉันเชื่อว่านี่เป็นสาเหตุที่DependentHandle
ไม่เปิดเผยโดยตรง - คุณไม่ต้องการยุ่งกับสิ่งต่าง ๆ และทำให้หน่วยความจำรั่วไหล สิ่งที่ดีที่สุดต่อไปคือ a 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
}