Tujuan Buang adalah membebaskan sumber daya yang tidak dikelola. Itu perlu dilakukan di beberapa titik, kalau tidak mereka tidak akan pernah dibersihkan. Pengumpul sampah tidak tahu cara memanggil DeleteHandle()
variabel tipe IntPtr
, tidak tahu apakah perlu memanggil atau tidak DeleteHandle()
.
Catatan : Apa itu sumber daya yang tidak dikelola ? Jika Anda menemukannya di Microsoft .NET Framework: ini dikelola. Jika Anda pergi sendiri di MSDN, itu tidak dikelola. Apa pun yang Anda gunakan untuk P / Panggil panggilan untuk keluar dari dunia nyaman yang bagus dari semua yang tersedia untuk Anda di .NET Framework tidak dikelola - dan sekarang Anda bertanggung jawab untuk membersihkannya.
Objek yang Anda buat perlu mengekspos beberapa metode, yang dapat dipanggil oleh dunia luar, untuk membersihkan sumber daya yang tidak dikelola. Metode ini dapat dinamai apa pun yang Anda suka:
public void Cleanup()
atau
public void Shutdown()
Tetapi sebaliknya ada nama standar untuk metode ini:
public void Dispose()
Bahkan ada antarmuka yang dibuat IDisposable
,, yang hanya memiliki satu metode:
public interface IDisposable
{
void Dispose()
}
Jadi Anda membuat objek Anda mengekspos IDisposable
antarmuka, dan dengan cara itu Anda berjanji bahwa Anda telah menulis metode tunggal untuk membersihkan sumber daya yang tidak dikelola:
public void Dispose()
{
Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
}
Dan kamu sudah selesai. Kecuali Anda bisa berbuat lebih baik.
Bagaimana jika objek Anda telah mengalokasikan System.Drawing.Bitmap 250MB (mis. Kelas Bitmap yang dikelola .NET) sebagai semacam bingkai penyangga? Tentu, ini adalah objek .NET yang dikelola, dan pengumpul sampah akan membebaskannya. Tapi apakah Anda benar-benar ingin meninggalkan memori 250MB hanya duduk di sana - menunggu pengumpul sampah akhirnya datang dan membebaskannya? Bagaimana jika ada koneksi database terbuka ? Tentunya kita tidak ingin koneksi itu terbuka, menunggu GC menyelesaikan objek.
Jika pengguna telah menelepon Dispose()
(artinya mereka tidak lagi berencana untuk menggunakan objek) mengapa tidak menyingkirkan bitmap yang boros dan koneksi database?
Jadi sekarang kita akan:
- menyingkirkan sumber daya yang tidak dikelola (karena kita harus), dan
- singkirkan sumber daya yang dikelola (karena kami ingin membantu)
Jadi, mari perbarui Dispose()
metode kami untuk menyingkirkan objek yang dikelola tersebut:
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;
}
}
Dan semuanya baik-baik saja, kecuali Anda bisa berbuat lebih baik !
Bagaimana jika orang tersebut lupa memanggil Dispose()
objek Anda? Kemudian mereka akan membocorkan beberapa sumber daya yang tidak dikelola !
Catatan: Mereka tidak akan membocorkan sumber daya yang dikelola , karena pada akhirnya pengumpul sampah akan berjalan, di utas latar belakang, dan membebaskan memori yang terkait dengan objek yang tidak digunakan. Ini akan termasuk objek Anda, dan semua objek terkelola yang Anda gunakan (misalnya, Bitmap
dan DbConnection
).
Jika orang itu lupa menelepon Dispose()
, kita masih bisa menyimpan bacon mereka! Kami masih memiliki cara untuk menyebutnya untuk mereka: ketika pengumpul sampah akhirnya berhasil membebaskan (yaitu menyelesaikan) objek kami.
Catatan: Pengumpul sampah pada akhirnya akan membebaskan semua objek yang dikelola. Ketika itu terjadi, ia memanggil Finalize
metode pada objek. GC tidak tahu, atau peduli, tentang metode Buang Anda . Itu hanya nama yang kami pilih untuk metode yang kami sebut ketika kami ingin menyingkirkan hal-hal yang tidak dikelola.
Penghancuran objek kita oleh pengumpul Sampah adalah waktu yang tepat untuk membebaskan sumber daya tak terkelola yang sial itu. Kami melakukan ini dengan mengganti Finalize()
metode.
Catatan: Di C #, Anda tidak secara eksplisit menimpa Finalize()
metode ini. Anda menulis sebuah metode yang terlihat seperti sebuah C ++ destructor , dan compiler mengambil bahwa untuk menjadi implementasi dari Finalize()
metode:
~MyObject()
{
//we're being finalized (i.e. destroyed), call Dispose in case the user forgot to
Dispose(); //<--Warning: subtle bug! Keep reading!
}
Tetapi ada bug dalam kode itu. Anda lihat, pengumpul sampah berjalan di utas latar belakang ; Anda tidak tahu urutan dua objek dihancurkan. Sangat mungkin bahwa dalam Dispose()
kode Anda , objek terkelola yang Anda coba singkirkan (karena Anda ingin membantu) sudah tidak ada lagi:
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;
}
}
Jadi yang Anda butuhkan adalah cara untuk Finalize()
mengatakan Dispose()
bahwa itu tidak boleh menyentuh sumber daya yang dikelola (karena mereka mungkin tidak ada lagi), sementara masih membebaskan sumber daya yang tidak dikelola.
Pola standar untuk melakukan ini adalah memiliki Finalize()
dan Dispose()
keduanya memanggil metode ketiga (!); tempat Anda menyampaikan pepatah Boolean jika Anda memanggilnya dari Dispose()
(berlawanan dengan Finalize()
), artinya aman untuk sumber daya yang dikelola gratis.
Metode internal ini dapat diberi beberapa nama arbitrer seperti "CoreDispose", atau "MyInternalDispose", tetapi adalah tradisi untuk menyebutnya Dispose(Boolean)
:
protected void Dispose(Boolean disposing)
Tetapi nama parameter yang lebih bermanfaat mungkin:
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;
}
}
}
Dan Anda mengubah penerapan IDisposable.Dispose()
metode ini menjadi:
public void Dispose()
{
Dispose(true); //I am calling you from Dispose, it's safe
}
dan finalizer Anda ke:
~MyObject()
{
Dispose(false); //I am *not* calling you from Dispose, it's *not* safe
}
Catatan : Jika objek Anda turun dari objek yang mengimplementasikan Dispose
, maka jangan lupa untuk memanggil basisnya Buang metode ketika Anda menimpa Buang:
public override void Dispose()
{
try
{
Dispose(true); //true: safe to free managed resources
}
finally
{
base.Dispose();
}
}
Dan semuanya baik-baik saja, kecuali Anda bisa berbuat lebih baik !
Jika pengguna memanggil Dispose()
objek Anda, maka semuanya telah dibersihkan. Kemudian, ketika pemulung datang dan memanggil Selesai, ia akan memanggil Dispose
lagi.
Tidak hanya boros, tetapi jika objek Anda memiliki referensi sampah ke objek yang sudah Anda buang dari panggilan terakhirDispose()
, Anda akan mencoba membuangnya lagi!
Anda akan melihat dalam kode saya, saya berhati-hati untuk menghapus referensi ke objek yang telah saya buang, jadi saya tidak mencoba untuk memanggil Dispose
referensi objek sampah. Tapi itu tidak menghentikan bug halus masuk.
Ketika pengguna memanggil Dispose()
: pegangan CursorFileBitmapIconServiceHandle dihancurkan. Kemudian ketika pengumpul sampah berjalan, ia akan mencoba untuk menghancurkan pegangan yang sama lagi.
protected void Dispose(Boolean iAmBeingCalledFromDisposeAndNotFinalize)
{
//Free unmanaged resources
Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle); //<--double destroy
...
}
Cara Anda memperbaiki ini adalah memberi tahu pengumpul sampah bahwa tidak perlu repot menyelesaikan objek - sumber dayanya sudah dibersihkan, dan tidak ada lagi pekerjaan yang diperlukan. Anda melakukan ini dengan memanggil GC.SuppressFinalize()
dalam Dispose()
metode:
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
}
Sekarang setelah pengguna menelepon Dispose()
, kami memiliki:
- membebaskan sumber daya yang tidak dikelola
- membebaskan sumber daya yang dikelola
Tidak ada gunanya dalam GC menjalankan finalizer - semuanya diurus.
Tidak bisakah saya menggunakan Finalisasi untuk membersihkan sumber daya yang tidak dikelola?
Dokumentasi untuk Object.Finalize
mengatakan:
Metode Finalisasi digunakan untuk melakukan operasi pembersihan pada sumber daya yang tidak dikelola yang dimiliki oleh objek saat ini sebelum objek dihancurkan.
Tetapi dokumentasi MSDN juga mengatakan, untuk IDisposable.Dispose
:
Melakukan tugas yang ditentukan aplikasi yang terkait dengan membebaskan, melepaskan, atau mengatur ulang sumber daya yang tidak dikelola.
Jadi yang mana? Yang mana tempat bagi saya untuk membersihkan sumber daya yang tidak dikelola? Jawabannya adalah:
Itu pilihanmu! Tapi pilih Dispose
.
Anda tentu dapat menempatkan pembersihan yang tidak dikelola di finalizer:
~MyObject()
{
//Free unmanaged resources
Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
//A C# destructor automatically calls the destructor of its base class.
}
Masalahnya adalah Anda tidak tahu kapan pemulung akan menyelesaikan objek Anda. Sumber daya asli Anda yang tidak dikelola, tidak dibutuhkan, dan tidak digunakan akan tetap ada sampai pemulung akhirnya berjalan. Maka itu akan memanggil metode finalizer Anda; membersihkan sumber daya yang tidak dikelola. Dokumentasi Object.Finalize menunjukkan ini:
Waktu yang tepat ketika finalizer dieksekusi tidak ditentukan. Untuk memastikan pelepasan sumber daya deterministik untuk instance kelas Anda, terapkan metode Tutup atau sediakan IDisposable.Dispose
implementasi.
Ini adalah keutamaan menggunakan Dispose
untuk membersihkan sumber daya yang tidak dikelola; Anda mengenal, dan mengontrol, ketika sumber daya yang tidak dikelola dibersihkan. Kehancuran mereka "deterministik" .
Untuk menjawab pertanyaan awal Anda: Mengapa tidak melepaskan memori sekarang, daripada ketika GC memutuskan untuk melakukannya? Saya memiliki perangkat lunak pengenal wajah yang perlu menyingkirkan 530 MB gambar internal sekarang , karena mereka tidak lagi diperlukan. Ketika kita tidak melakukannya: mesin terhenti.
Pembacaan Bonus
Bagi siapa pun yang menyukai gaya jawaban ini (menjelaskan mengapa , bagaimana caranya menjadi jelas), saya sarankan Anda membaca Bab Satu dari COM Essential Don Box:
Dalam 35 halaman dia menjelaskan masalah menggunakan objek biner, dan menemukan COM di depan mata Anda. Setelah Anda menyadari mengapa COM, 300 halaman sisanya jelas, dan hanya merinci implementasi Microsoft.
Saya pikir setiap programmer yang pernah berurusan dengan objek atau COM harus, paling tidak, membaca bab pertama. Itu adalah penjelasan terbaik dari apa pun.
Bacaan Bonus Ekstra
Ketika semua yang Anda tahu salah oleh Eric Lippert
Karenanya memang sangat sulit untuk menulis finalizer yang benar, dan saran terbaik yang dapat saya berikan kepada Anda adalah untuk tidak mencoba .