จุดกำจัดคือการเพิ่มทรัพยากรที่ไม่มีการจัดการ มันจำเป็นต้องทำในบางจุดมิฉะนั้นพวกเขาจะไม่ทำความสะอาด เก็บขยะไม่ทราบวิธีการเรียกร้องDeleteHandle()
ในตัวแปรชนิดIntPtr
ก็ไม่ทราบว่าจะDeleteHandle()
ได้หรือไม่ก็ต้องโทร
หมายเหตุ : ทรัพยากรที่ไม่มีการจัดการคืออะไร? หากคุณพบมันใน Microsoft .NET Framework: มีการจัดการ หากคุณไปรอบ ๆ MSDN ด้วยตัวคุณเองมันก็ไม่มีการจัดการ สิ่งใดก็ตามที่คุณใช้การโทรแบบ P / Invoke เพื่อออกไปข้างนอกโลกที่แสนสบายของทุกสิ่งที่มีให้คุณใน. NET Framework นั้นไม่มีการจัดการ - และตอนนี้คุณต้องรับผิดชอบในการทำความสะอาด
วัตถุที่คุณสร้างขึ้นต้องเปิดเผยวิธีการบางอย่างที่โลกภายนอกสามารถเรียกใช้เพื่อล้างทรัพยากรที่ไม่มีการจัดการ วิธีนี้สามารถตั้งชื่อตามที่คุณต้องการ:
public void Cleanup()
หรือ
public void Shutdown()
แต่มีชื่อมาตรฐานสำหรับวิธีนี้แทน:
public void Dispose()
มีแม้กระทั่งอินเตอร์เฟสที่สร้างขึ้นIDisposable
ซึ่งมีเพียงหนึ่งวิธี:
public interface IDisposable
{
void Dispose()
}
ดังนั้นคุณทำให้วัตถุของคุณเปิดเผยIDisposable
อินเทอร์เฟซและวิธีที่คุณสัญญาว่าคุณได้เขียนวิธีการเดียวเพื่อล้างทรัพยากรที่ไม่มีการจัดการของคุณ:
public void Dispose()
{
Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
}
และคุณทำเสร็จแล้ว ยกเว้นคุณสามารถทำได้ดีกว่า
จะเกิดอะไรขึ้นถ้าวัตถุของคุณจัดสรรSystem.Drawing.Bitmap ขนาด 250MB (เช่นคลาส. NET จัดการบิตแมป) เป็นบัฟเฟอร์เฟรมบางประเภท แน่นอนว่านี่เป็นวัตถุ. NET ที่ได้รับการจัดการและตัวรวบรวมขยะจะทำให้เป็นอิสระ แต่คุณต้องการที่จะออกจากหน่วยความจำ 250MB เพียงแค่นั่งอยู่ที่นั่นรอให้นักสะสมขยะทิ้งตัวและปล่อยให้เป็นอิสระในที่สุด ? เกิดอะไรขึ้นถ้ามีการเชื่อมต่อฐานข้อมูลเปิด ? แน่นอนว่าเราไม่ต้องการให้การเชื่อมต่อนั้นเปิดอยู่รอให้ GC ทำการทำให้วัตถุเสร็จสิ้น
หากผู้ใช้ได้เรียกDispose()
(หมายถึงพวกเขาไม่ได้วางแผนที่จะใช้วัตถุอีกต่อไป) ทำไมไม่กำจัดบิตแมปที่สิ้นเปลืองและการเชื่อมต่อฐานข้อมูลเหล่านั้นออกไป?
ดังนั้นตอนนี้เราจะ:
- กำจัดทรัพยากรที่ไม่มีการจัดการ (เพราะเราต้อง) และ
- กำจัดทรัพยากรที่มีการจัดการ (เพราะเราต้องการเป็นประโยชน์)
ดังนั้นให้ปรับปรุงDispose()
วิธีการของเราเพื่อกำจัดวัตถุที่มีการจัดการเหล่านั้น:
public void Dispose()
{
//Free unmanaged resources
Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
//Free managed resources too
if (this.databaseConnection != null)
{
this.databaseConnection.Dispose();
this.databaseConnection = null;
}
if (this.frameBufferImage != null)
{
this.frameBufferImage.Dispose();
this.frameBufferImage = null;
}
}
และทุกอย่างก็ดียกเว้นคุณทำได้ดีกว่า !
เกิดอะไรขึ้นถ้าคนลืมที่จะโทรหาDispose()
วัตถุของคุณ? จากนั้นพวกเขาก็จะรั่วไหลทรัพยากรที่ไม่มีการจัดการบางอย่าง!
หมายเหตุ:พวกเขาจะไม่รั่วไหลทรัพยากรที่มีการจัดการเพราะในที่สุดตัวรวบรวมขยะจะทำงานบนเธรดพื้นหลังและเพิ่มหน่วยความจำที่เกี่ยวข้องกับวัตถุที่ไม่ได้ใช้ ซึ่งจะรวมถึงวัตถุของคุณและวัตถุที่มีการจัดการใด ๆ ที่คุณใช้ (เช่นBitmap
และDbConnection
)
หากบุคคลนั้นลืมโทรDispose()
เรายังคงสามารถบันทึกเบคอนได้! เรายังคงมีวิธีเรียกมันสำหรับพวกเขา: ในที่สุดเมื่อตัวรวบรวมขยะได้รับการแก้ไข (เช่นการจบ) วัตถุของเรา
หมายเหตุ:ในที่สุดตัวรวบรวมขยะจะทำให้วัตถุที่ได้รับการจัดการหมดสิ้น เมื่อใดก็จะเรียกFinalize
วิธีการบนวัตถุ GC ไม่รู้เกี่ยวกับวิธีการกำจัดของคุณ นั่นเป็นเพียงชื่อที่เราเลือกสำหรับวิธีการที่เราเรียกเมื่อเราต้องการกำจัดสิ่งที่ไม่มีการจัดการ
การทำลายวัตถุของเราโดย Garbage Collector เป็นเวลาที่เหมาะสมในการปลดปล่อยทรัพยากรที่ไม่มีการจัดการ เราทำสิ่งนี้โดยการเอาชนะFinalize()
เมธอด
หมายเหตุ:ใน C # คุณจะไม่แทนที่Finalize()
วิธีการอย่างชัดเจน คุณเขียนวิธีที่ดูเหมือนc ++ destructorและคอมไพเลอร์จะใช้เวลาที่จะต้องมีการดำเนินการของคุณFinalize()
วิธีการ:
~MyObject()
{
//we're being finalized (i.e. destroyed), call Dispose in case the user forgot to
Dispose(); //<--Warning: subtle bug! Keep reading!
}
แต่มีข้อผิดพลาดในรหัสนั้น คุณเห็นเก็บขยะทำงานบนด้ายพื้นหลัง ; คุณไม่ทราบลำดับที่วัตถุทั้งสองถูกทำลาย อาจเป็นไปได้ว่าในDispose()
รหัสของคุณวัตถุที่จัดการที่คุณพยายามกำจัด (เพราะคุณต้องการให้ความช่วยเหลือ) ไม่มีอยู่อีกต่อไป:
public void Dispose()
{
//Free unmanaged resources
Win32.DestroyHandle(this.gdiCursorBitmapStreamFileHandle);
//Free managed resources too
if (this.databaseConnection != null)
{
this.databaseConnection.Dispose(); //<-- crash, GC already destroyed it
this.databaseConnection = null;
}
if (this.frameBufferImage != null)
{
this.frameBufferImage.Dispose(); //<-- crash, GC already destroyed it
this.frameBufferImage = null;
}
}
ดังนั้นสิ่งที่คุณต้องการคือวิธีที่Finalize()
จะบอกDispose()
ว่าไม่ควรแตะต้องทรัพยากรที่มีการจัดการใด ๆ (เพราะอาจไม่มีอยู่อีกต่อไป) ในขณะที่ยังคงปล่อยทรัพยากรที่ไม่มีการจัดการ
รูปแบบมาตรฐานในการทำเช่นนี้คือต้องมีFinalize()
และDispose()
เรียกวิธีที่สาม (!); ที่คุณผ่านบูลีนโดยบอกว่าคุณกำลังโทรหาจากDispose()
(ตรงข้ามกับFinalize()
) หมายความว่าปลอดภัยที่จะได้รับทรัพยากรที่มีการจัดการฟรี
วิธีการภายในนี้อาจได้รับชื่อตามอำเภอใจเช่น "CoreDispose" หรือ "MyInternalDispose" แต่เป็นประเพณีที่เรียกว่าDispose(Boolean)
:
protected void Dispose(Boolean disposing)
แต่ชื่อพารามิเตอร์ที่เป็นประโยชน์มากขึ้นอาจเป็น:
protected void Dispose(Boolean itIsSafeToAlsoFreeManagedObjects)
{
//Free unmanaged resources
Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
//Free managed resources too, but only if I'm being called from Dispose
//(If I'm being called from Finalize then the objects might not exist
//anymore
if (itIsSafeToAlsoFreeManagedObjects)
{
if (this.databaseConnection != null)
{
this.databaseConnection.Dispose();
this.databaseConnection = null;
}
if (this.frameBufferImage != null)
{
this.frameBufferImage.Dispose();
this.frameBufferImage = null;
}
}
}
และคุณเปลี่ยนวิธีการใช้งานของคุณเป็นIDisposable.Dispose()
:
public void Dispose()
{
Dispose(true); //I am calling you from Dispose, it's safe
}
และผู้เข้ารอบสุดท้ายของคุณไปที่:
~MyObject()
{
Dispose(false); //I am *not* calling you from Dispose, it's *not* safe
}
หมายเหตุ : หากวัตถุของคุณเคลื่อนลงมาจากวัตถุที่นำไปใช้Dispose
อย่าลืมเรียกวิธีกำจัดทิ้งฐานเมื่อคุณแทนที่ทิ้ง:
public override void Dispose()
{
try
{
Dispose(true); //true: safe to free managed resources
}
finally
{
base.Dispose();
}
}
และทุกอย่างก็ดียกเว้นคุณทำได้ดีกว่า !
หากผู้ใช้โทรหาDispose()
วัตถุของคุณทุกอย่างก็จะถูกกำจัด ในภายหลังเมื่อตัวรวบรวมขยะเข้ามาและโทรออกแล้วมันจะโทรDispose
อีกครั้ง
ไม่เพียง แต่จะสิ้นเปลือง แต่หากวัตถุของคุณมีการอ้างอิงขยะไปยังวัตถุที่คุณกำจัดไปแล้วจากการโทรครั้งสุดท้ายไปDispose()
คุณจะพยายามกำจัดพวกเขาอีกครั้ง!
คุณจะสังเกตเห็นในรหัสของฉันฉันระมัดระวังที่จะลบการอ้างอิงไปยังวัตถุที่ฉันได้จำหน่ายดังนั้นฉันจะไม่พยายามโทรหาDispose
การอ้างอิงวัตถุขยะ แต่นั่นไม่ได้หยุดข้อผิดพลาดเล็กน้อยจากการคืบคลานเข้ามา
เมื่อผู้ใช้โทรDispose()
: หมายเลขอ้างอิงCursorFileBitmapIconServiceHandleถูกทำลาย ต่อมาเมื่อตัวรวบรวมขยะทำงานจะพยายามทำลายที่จับเดียวกันอีกครั้ง
protected void Dispose(Boolean iAmBeingCalledFromDisposeAndNotFinalize)
{
//Free unmanaged resources
Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle); //<--double destroy
...
}
วิธีที่คุณแก้ไขปัญหานี้คือแจ้งให้ผู้รวบรวมขยะทราบว่าไม่จำเป็นต้องกังวลกับการจบวัตถุ - ทรัพยากรได้ถูกล้างข้อมูลแล้วและไม่จำเป็นต้องทำงานอีกต่อไป คุณทำได้โดยการโทรGC.SuppressFinalize()
ในDispose()
วิธีการ:
public void Dispose()
{
Dispose(true); //I am calling you from Dispose, it's safe
GC.SuppressFinalize(this); //Hey, GC: don't bother calling finalize later
}
ตอนนี้ผู้ใช้เรียกDispose()
เราได้:
- ปล่อยทรัพยากรที่ไม่มีการจัดการ
- การจัดการทรัพยากรที่เป็นอิสระ
ไม่มีประเด็นใดที่ GC จะใช้งาน finalizer - ทุกอย่างได้รับการดูแล
ฉันไม่สามารถใช้ Finalize เพื่อล้างทรัพยากรที่ไม่มีการจัดการได้หรือไม่
เอกสารสำหรับObject.Finalize
พูดว่า:
กระบวนการ Finalize จะใช้เพื่อดำเนินการล้างข้อมูลบนทรัพยากรที่ไม่มีการจัดการที่มีอยู่ในวัตถุปัจจุบันก่อนที่วัตถุจะถูกทำลาย
แต่เอกสาร MSDN ก็บอกว่าสำหรับIDisposable.Dispose
:
ดำเนินงานที่กำหนดโดยแอปพลิเคชันที่เกี่ยวข้องกับการปลดปล่อยปล่อยหรือรีเซ็ตทรัพยากรที่ไม่มีการจัดการ
แล้วมันคืออะไร? อันไหนที่ฉันจะล้างทรัพยากรที่ไม่มีการจัดการ? คำตอบคือ:
มันเป็นทางเลือกของคุณ! Dispose
แต่เลือก
แน่นอนคุณสามารถวางการล้างข้อมูลที่ไม่มีการจัดการของคุณใน finalizer:
~MyObject()
{
//Free unmanaged resources
Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
//A C# destructor automatically calls the destructor of its base class.
}
ปัญหาที่เกิดขึ้นคือคุณไม่มีความคิดว่าเมื่อใดที่ตัวรวบรวมขยะจะได้รับการแก้ไขวัตถุของคุณ ทรัพยากรดั้งเดิมที่ไม่ได้รับการจัดการและไม่ต้องการและไม่ได้ใช้ของคุณจะติดอยู่รอบ ๆ จนกว่าตัวรวบรวมขยะจะทำงานในที่สุด จากนั้นจะเรียกวิธีการสุดท้ายของคุณ ล้างทรัพยากรที่ไม่มีการจัดการ เอกสารของObject.Finalizeชี้สิ่งนี้:
เวลาที่แน่นอนเมื่อดำเนินการขั้นสุดท้ายไม่ได้กำหนด เพื่อให้แน่ใจว่ามีการปล่อยทรัพยากรอย่างไม่แน่นอนสำหรับอินสแตนซ์ของคลาสของคุณให้ใช้เมธอดปิดหรือจัดให้มีIDisposable.Dispose
การนำไปใช้
นี่คือข้อดีของการใช้Dispose
เพื่อล้างทรัพยากรที่ไม่มีการจัดการ คุณจะได้รับรู้และควบคุมเมื่อทรัพยากรที่ไม่มีการจัดการถูกทำความสะอาด การทำลายของพวกเขาคือ"กำหนด"
หากต้องการตอบคำถามเดิมของคุณ: ทำไมไม่ปล่อยหน่วยความจำตอนนี้แทนที่จะตัดสินใจเมื่อ GC ตัดสินใจทำ? ฉันมีซอฟต์แวร์การจดจำใบหน้าที่ความต้องการที่จะกำจัดของ 530 MB ของภาพภายในในขณะนี้เนื่องจากพวกเขากำลังไม่จำเป็น เมื่อเราทำไม่ได้เครื่องจะทำการหยุดการแลกเปลี่ยน
การอ่านโบนัส
สำหรับทุกคนที่ชอบสไตล์ของคำตอบนี้ (อธิบายสาเหตุดังนั้นวิธีที่ชัดเจน) ฉันขอแนะนำให้คุณอ่านบทที่หนึ่งใน COM Essential ของ Don Box:
ใน 35 หน้าเขาอธิบายปัญหาของการใช้วัตถุไบนารีและประดิษฐ์ COM ต่อหน้าต่อตาคุณ เมื่อคุณตระหนักถึงสาเหตุของ COM หน้าเว็บที่เหลืออีก 300 หน้านั้นชัดเจนและเพียงแสดงรายละเอียดการใช้งานของ Microsoft
ฉันคิดว่าโปรแกรมเมอร์ทุกคนที่เคยจัดการกับวัตถุหรือ COM ควรอ่านตอนแรกอย่างน้อยที่สุด มันเป็นคำอธิบายที่ดีที่สุดสำหรับทุกสิ่ง
การอ่านโบนัสพิเศษ
เมื่อทุกสิ่งที่คุณรู้ว่าผิดโดย Eric Lippert
มันจึงเป็นเรื่องยากมากอย่างแน่นอนที่จะเขียน finalizer ที่ถูกต้องและคำแนะนำที่ดีที่สุดที่ฉันสามารถให้คุณคือการไม่พยายาม