Kapan menggunakan referensi yang lemah di .Net?

56

Saya belum menemukan situasi di mana saya harus menggunakan tipe WeakReference di .Net, tetapi kepercayaan yang populer sepertinya harus digunakan dalam cache. Dr Jon Harrop memberikan kasus yang sangat baik terhadap penggunaan WeakReferences di cache dalam bukunya jawaban untuk ini pertanyaan.

Saya juga sering mendengar pengembang AS3 berbicara tentang menggunakan referensi yang lemah untuk menghemat jejak memori tetapi berdasarkan percakapan saya sepertinya menambah kompleksitas tanpa harus mencapai tujuan yang diinginkan, dan perilaku runtime agak tidak dapat diprediksi. Sedemikian rupa sehingga banyak yang menyerah dan mengelola penggunaan memori dengan lebih hati-hati / mengoptimalkan kode mereka menjadi kurang intensif memori (atau membuat trade-off lebih banyak siklus CPU dan jejak memori yang lebih kecil).

Dr Jon Harrop juga menunjukkan dalam jawabannya bahwa referensi lemah. Net tidak lunak, dan ada koleksi agresif referensi lemah di gen0. Menurut MSDN , referensi panjang yang lemah memberi Anda potensi untuk membuat ulang objek but the state of the object remains unpredictable.,!

Mengingat karakteristik ini, saya tidak dapat memikirkan situasi di mana referensi yang lemah akan berguna, mungkin seseorang dapat mencerahkan saya?

theburningmonk
sumber
3
Anda sudah menguraikan potensi kegunaannya. Tentu saja ada cara lain untuk mendekati situasi ini, tetapi ada lebih dari satu cara untuk menguliti kucing. Jika Anda mencari bukti-peluru "Anda harus selalu menggunakan Referensi Weak ketika X", saya ragu Anda akan menemukannya.
2
@ itsme86 - Saya tidak mencari case use peluru-bukti, hanya referensi lemah yang cocok untuk dan masuk akal. Contoh penggunaan cache, karena referensi yang lemah dikumpulkan dengan penuh semangat sehingga hanya akan menyebabkan lebih banyak cache yang hilang bahkan ketika Anda memiliki banyak memori yang tersedia
4
Saya sedikit kecewa ini mendapatkan beberapa suara untuk ditutup. Saya tidak keberatan melihat jawaban atau diskusi tentang ini (di b4 "Stack Overflow bukan forum").
ta.speot.is
@ theburningmonk Yaitu offset sebagai imbalan atas perolehan memori. Dalam kerangka kerja saat ini, diragukan bahwa ada orang yang akan langsung mencapai alat WeakReference bahkan ketika menerapkan cache karena ada sistem caching komprehensif yang tersedia.
Ini adalah contoh rumit yang memalukan untuk menggunakannya (untuk Weak Event Pattern yang dijelaskan ta.speot.is di bawah ini)
Benjol

Jawaban:

39

Saya telah menemukan aplikasi praktis yang sah dari referensi yang lemah dalam tiga skenario dunia nyata berikut yang sebenarnya terjadi pada saya secara pribadi:

Aplikasi 1: Penangan acara

Anda seorang pengusaha. Perusahaan Anda menjual kontrol jalur percikan untuk WPF. Penjualan besar tetapi biaya dukungan membunuh Anda. Terlalu banyak pelanggan mengeluh tentang CPU hogging dan kebocoran memori ketika mereka menggulir layar penuh garis percikan. Masalahnya adalah aplikasi mereka membuat garis percikan baru saat mereka terlihat tetapi pengikatan data mencegah yang lama dikumpulkan. Apa yang kamu kerjakan?

Memperkenalkan referensi yang lemah antara pengikatan data dan kontrol Anda sehingga pengikatan data saja tidak akan lagi mencegah kontrol Anda dari pengumpulan sampah. Kemudian tambahkan finalizer ke kontrol Anda yang meruntuhkan ikatan data saat dikumpulkan.

Aplikasi 2: Grafik yang dapat diubah

Anda adalah John Carmack berikutnya. Anda telah menemukan representasi berbasis grafik baru dari permukaan subdivisi hirarkis yang membuat permainan Tim Sweeney terlihat seperti Nintendo Wii. Jelas saya tidak akan memberi tahu Anda dengan tepat bagaimana cara kerjanya tetapi semuanya berpusat pada grafik yang bisa berubah ini di mana tetangga dari sebuah simpul dapat ditemukan di a Dictionary<Vertex, SortedSet<Vertex>>. Topologi grafik terus berubah saat pemain berputar. Hanya ada satu masalah: struktur data Anda menumpahkan subgraph yang tidak dapat dijangkau saat dijalankan dan Anda harus menghapusnya atau Anda akan membocorkan memori. Untungnya Anda jenius sehingga Anda tahu ada kelas algoritma yang dirancang khusus untuk mencari dan mengumpulkan subgraph yang tidak dapat dijangkau: pengumpul sampah! Anda membaca monografi luar biasa Richard Jones tentang masalah initetapi itu membuat Anda bingung dan khawatir tentang tenggat waktu Anda yang sudah dekat. Apa yang kamu kerjakan?

Cukup dengan mengganti Dictionarytabel hash Anda dengan lemah, Anda dapat mendukung GC yang ada dan memilikinya secara otomatis mengumpulkan subgraf yang tidak dapat dijangkau untuk Anda! Kembali ke membalik-balik iklan Ferrari.

Aplikasi 3: Dekorasi pohon

Anda tergantung dari langit-langit ruang cyclindrical di keyboard. Anda punya 60 detik untuk menyaring beberapa DATA BESAR sebelum seseorang menemukan Anda. Anda datang dipersiapkan dengan parser berbasis aliran yang indah yang mengandalkan GC untuk mengumpulkan fragmen AST setelah dianalisis. Tetapi Anda menyadari bahwa Anda membutuhkan metadata tambahan pada setiap AST Nodedan Anda membutuhkannya dengan cepat. Apa yang kamu kerjakan?

Anda bisa menggunakan a Dictionary<Node, Metadata>untuk mengaitkan metadata dengan setiap node tetapi, kecuali Anda menghapusnya, referensi kuat dari kamus ke node AST lama akan membuatnya tetap hidup dan membocorkan memori. Solusinya adalah tabel hash yang lemah yang hanya menyimpan referensi lemah untuk kunci dan sampah mengumpulkan ikatan nilai kunci ketika kunci menjadi tidak terjangkau. Kemudian, ketika AST node menjadi tidak terjangkau mereka mengumpulkan sampah dan mengikat nilai kunci mereka dihapus dari kamus meninggalkan metadata yang sesuai tidak terjangkau sehingga juga dikumpulkan. Maka yang harus Anda lakukan setelah loop utama Anda berakhir adalah meluncur ke atas melalui lubang udara mengingat untuk menggantinya tepat ketika penjaga keamanan masuk.

Perhatikan bahwa dalam ketiga aplikasi dunia nyata ini yang benar-benar terjadi pada saya, saya ingin GC mengumpulkan seagresif mungkin. Itu sebabnya ini adalah aplikasi yang sah. Semua orang salah.

Jon Harrop
sumber
2
Referensi yang lemah tidak akan berfungsi untuk aplikasi 2 jika subgraph yang tidak terjangkau berisi siklus. Ini karena tabel hash yang lemah biasanya memiliki referensi yang lemah ke kunci, tetapi referensi yang kuat untuk nilai-nilai. Anda akan memerlukan tabel hash yang mempertahankan referensi kuat ke nilai-nilai hanya ketika kunci masih dapat dijangkau -> lihat ephemerons ( ConditionalWeakTabledalam. NET).
Daniel
@Aniel Bukankah GC seharusnya mampu menangani siklus yang tidak terjangkau? Bagaimana ini tidak dikumpulkan ketika siklus referensi kuat yang tidak terjangkau akan dikumpulkan?
binki
Oh, kurasa begitu. Saya hanya berasumsi bahwa ConditionalWeakTableitulah yang akan digunakan aplikasi 2 dan 3 sedangkan beberapa orang di postingan lain benar-benar menggunakannya Dictionary<WeakReference, T>. Tidak tahu mengapa — Anda selalu berakhir dengan satu ton nol WeakReferencedengan nilai yang tidak dapat diakses oleh tombol apa pun terlepas dari bagaimana Anda melakukannya. Ridik.
binki
@binki: "Bukankah GC seharusnya mampu menangani siklus yang tidak terjangkau? Bagaimana ini tidak dikumpulkan ketika siklus referensi kuat yang tidak terjangkau akan dikumpulkan?". Anda memiliki kamus yang dikunci pada objek unik yang tidak dapat dibuat ulang. Ketika salah satu objek utama Anda menjadi tidak terjangkau, dapat berupa sampah yang dikumpulkan tetapi nilai yang sesuai dalam kamus bahkan tidak akan dianggap tidak terjangkau secara teoritis karena kamus biasa akan menyimpan referensi yang kuat untuk itu, menjaganya tetap hidup. Jadi Anda menggunakan kamus yang lemah.
Jon Harrop
@Aniel: "Referensi lemah tidak akan berfungsi untuk aplikasi 2 jika subgraph yang tidak terjangkau mengandung siklus. Ini karena tabel hash yang lemah biasanya memiliki referensi yang lemah ke kunci, tetapi referensi yang kuat untuk nilai-nilai. Anda akan membutuhkan tabel hash yang mempertahankan referensi yang kuat ke nilai hanya saat kunci masih dapat dijangkau ". Iya. Anda mungkin lebih baik menyandikan grafik secara langsung dengan edge sebagai pointer sehingga GC akan mengumpulkannya sendiri.
Jon Harrop
19

Mengingat karakteristik ini, saya tidak dapat memikirkan situasi di mana referensi yang lemah akan berguna, mungkin seseorang dapat mencerahkan saya?

Dokumen Microsoft, Pola Kejadian Lemah .

Dalam aplikasi, ada kemungkinan bahwa penangan yang dilampirkan ke sumber acara tidak akan dihancurkan dalam koordinasi dengan objek pendengar yang melampirkan penangan ke sumber. Situasi ini dapat menyebabkan kebocoran memori. Windows Presentation Foundation (WPF) memperkenalkan pola desain yang dapat digunakan untuk mengatasi masalah ini, dengan menyediakan kelas manajer khusus untuk acara-acara tertentu dan mengimplementasikan antarmuka pada pendengar untuk acara tersebut. Pola desain ini dikenal sebagai pola acara yang lemah.

...

Pola acara lemah dirancang untuk mengatasi masalah kebocoran memori ini. Pola acara yang lemah dapat digunakan kapan pun pendengar perlu mendaftar untuk suatu acara, tetapi pendengar tidak secara eksplisit tahu kapan harus membatalkan pendaftaran. Pola acara yang lemah juga dapat digunakan kapan pun umur objek sumber melebihi umur objek yang berguna dari pendengar. (Dalam hal ini, berguna ditentukan oleh Anda.) Pola acara yang lemah memungkinkan pendengar untuk mendaftar dan menerima acara tanpa mempengaruhi karakteristik objek seumur hidup pendengar dengan cara apa pun. Akibatnya, referensi tersirat dari sumber tidak menentukan apakah pendengar memenuhi syarat untuk pengumpulan sampah. Referensi adalah referensi yang lemah, sehingga penamaan pola acara yang lemah dan API terkait. Pendengar dapat berupa sampah yang dikumpulkan atau dihancurkan, dan sumber dapat melanjutkan tanpa mempertahankan referensi penangan yang tidak dapat dikumpulkan ke objek yang sekarang dihancurkan.

ta.speot.is
sumber
URL itu otomatis memilih versi .NET terbaru (4,5 saat ini) di mana 'topik ini tidak lagi tersedia'. Memilih .NET 4.0 sebagai gantinya berfungsi ( msdn.microsoft.com/en-us/library/aa970850(v=vs.100).aspx )
maks.
13

Biarkan saya mengeluarkan ini dulu dan kembali ke sana:

WeakReference berguna ketika Anda ingin mengawasi objek, tetapi Anda TIDAK ingin pengamatan Anda untuk mencegah objek itu dikumpulkan

Jadi mari kita mulai dari awal:

- maaf sebelumnya untuk setiap pelanggaran yang tidak disengaja, tapi aku akan kembali ke level "Dick dan Jane" untuk sesaat karena seseorang tidak akan pernah bisa memberitahu audiensnya.

Jadi, ketika Anda punya objek X- mari kita tentukan itu sebagai contoh class Foo- itu TIDAK BISA hidup sendiri (sebagian besar benar); Dengan cara yang sama bahwa "Tidak ada manusia adalah sebuah pulau", hanya ada beberapa cara agar suatu objek dapat dipromosikan menjadi Pulau - meskipun itu disebut sebagai akar GC dalam CLR berbicara. Menjadi Root GC, atau memiliki rantai koneksi / referensi ke root GC, pada dasarnya adalah apa yang menentukan apakah Foo x = new Foo()sampah dikumpulkan atau tidak .

Jika Anda tidak dapat berjalan kembali ke root GC baik dengan menumpuk atau menumpuk berjalan, Anda menjadi yatim secara efektif, dan kemungkinan akan ditandai / dikumpulkan siklus berikutnya.

Pada titik ini, mari kita lihat beberapa contoh yang mengerikan:

Pertama, kami Foo:

public class Foo 
{
    private static volatile int _ref = 0;
    public event EventHandler FooEvent;
    public Foo()
    {
        _ref++;
        Console.WriteLine("I am #{0}", _ref);
    }
    ~Foo()
    {
        Console.WriteLine("#{0} dying!", _ref--);
    }
}

Cukup sederhana - ini bukan utas yang aman, jadi jangan coba-coba itu, tetapi simpan "jumlah referensi" kasar dari contoh dan pengurangan aktif saat selesai.

Sekarang mari kita lihat FooConsumer:

public class NastySingleton
{
    // Static member status is one way to "get promoted" to a GC root...
    private static NastySingleton _instance = new NastySingleton();
    public static NastySingleton Instance { get { return _instance;} }

    // testing out "Hard references"
    private Dictionary<Foo, int> _counter = new Dictionary<Foo,int>();
    // testing out "Weak references"
    private Dictionary<WeakReference, int> _weakCounter = new Dictionary<WeakReference,int>();

    // Creates a strong link to Foo instance
    public void ListenToThisFoo(Foo foo)
    {
        _counter[foo] = 0;
        foo.FooEvent += (o, e) => _counter[foo]++;
    }

    // Creates a weak link to Foo instance
    public void ListenToThisFooWeakly(Foo foo)
    {
        WeakReference fooRef = new WeakReference(foo);
        _weakCounter[fooRef] = 0;
        foo.FooEvent += (o, e) => _weakCounter[fooRef]++;
    }

    private void HandleEvent(object sender, EventArgs args, Foo originalfoo)
    {
        Console.WriteLine("Derp");
    }
}

Jadi kita punya objek yang sudah menjadi root GC sendiri (yah ... lebih spesifik, itu akan di-root melalui rantai langsung ke domain aplikasi yang menjalankan aplikasi ini, tapi itu topik lain) yang memiliki dua metode menempel ke Foocontoh - mari kita mengujinya:

// Our foo
var f = new Foo();

// Create a "hard reference"
NastySingleton.Instance.ListenToThisFoo(f);

// Ok, we're done with this foo
f = null;

// Force collection of all orphaned objects
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();

Sekarang, dari yang di atas, apakah Anda berharap objek-yang-pernah-disebut-oleh fitu "bisa dikoleksi"?

Tidak, karena ada objek lain sekarang memegang referensi untuk itu - Dictionarydalam Singletoncontoh statis itu.

Ok, mari kita coba pendekatan yang lemah:

f = new Foo();
NastySingleton.Instance.ListenToThisFooWeakly(f);

// Ok, we're done with this foo
f = null;

// Force collection of all orphaned objects
// This should collect # 2 - you'll see a "#2 dying"
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();

Sekarang, ketika kita memukul referensi kita ke Foo-yang-itu-sekali- f, tidak ada lagi referensi "keras" untuk objek, jadi itu bisa dikoleksi - yang WeakReferencedibuat oleh pendengar yang lemah tidak akan mencegah itu.

Kasus penggunaan yang baik:

  • Penangan acara (Meskipun membaca ini dulu: Lemahnya Acara di C # )

  • Anda punya situasi di mana Anda akan menyebabkan "referensi rekursif" (yaitu, objek A merujuk ke objek B, yang mengacu pada objek A, juga disebut sebagai "Memory Leak") (edit: derp, tentu saja ini bukan itu benar)

  • Anda ingin "menyiarkan" sesuatu ke koleksi objek, tetapi Anda tidak ingin menjadi benda yang membuat mereka tetap hidup; a List<WeakReference>dapat dipertahankan dengan mudah, dan bahkan dipangkas dengan menghapus di manaref.Target == null

Jerimbimball
sumber
1
Mengenai kasus penggunaan kedua Anda, pengumpul sampah menangani referensi melingkar dengan baik. "objek A merujuk ke objek B, yang mengacu pada objek A" jelas bukan kebocoran memori.
Joe Daley
@ JoDaley saya setuju. NET. GC menggunakan tanda dan sapuan algoritma yang (saya percaya saya mengingat ini dengan benar) menandai semua objek untuk koleksi dan kemudian mengikuti referensi dari "root" (referensi objek pada stack, objek statis), objek yang tidak ditandai untuk koleksi . Jika referensi melingkar ada tetapi tidak ada objek yang dapat diakses dari root, objek tidak ditandai untuk koleksi dan dengan demikian memenuhi syarat untuk koleksi.
ta.speot.is
1
@ JoDaley - Anda berdua, tentu saja, benar - bergegas ke sana sampai akhir ... Saya akan mengeditnya.
JerKimball
4

masukkan deskripsi gambar di sini

Seperti kebocoran logis yang benar-benar sulit dilacak sementara pengguna cenderung memperhatikan bahwa menjalankan perangkat lunak Anda untuk waktu yang lama cenderung mengambil lebih banyak dan lebih banyak memori dan semakin lama semakin lambat hingga mereka mulai ulang? Bukan saya.

Pertimbangkan apa yang terjadi jika, ketika pengguna meminta untuk menghapus sumber daya aplikasi di atas, Thing2gagal untuk menangani peristiwa semacam itu di bawah:

  1. Pointer
  2. Referensi yang kuat
  3. Referensi yang lemah

... dan di mana salah satu dari kesalahan semacam itu kemungkinan akan ditangkap selama pengujian, dan mana yang tidak akan dan akan terbang di bawah radar seperti bug pejuang siluman. Kepemilikan bersama, lebih sering daripada kebanyakan, adalah ide yang tidak masuk akal.


sumber
1

Contoh yang sangat ilustratif dari referensi lemah yang digunakan untuk efek yang baik adalah ConditionalWeakTable , yang digunakan oleh DLR (di antara tempat-tempat lain) untuk melampirkan "anggota" tambahan ke objek.

Anda tidak ingin tabel menjaga objek tetap hidup. Konsep ini tidak dapat bekerja tanpa referensi yang lemah.

Tapi sepertinya bagi saya bahwa semua penggunaan untuk referensi yang lemah datang lama setelah mereka ditambahkan ke bahasa, karena referensi yang lemah telah menjadi bagian dari .NET sejak versi 1.1. Sepertinya sesuatu yang ingin Anda tambahkan, sehingga kurangnya kehancuran deterministik tidak akan mendukung Anda sejauh menyangkut fitur bahasa.

GregRos
sumber
Saya benar-benar menemukan bahwa walaupun tabel menggunakan konsep referensi lemah, implementasi sebenarnya tidak melibatkan WeakReferencetipe, karena situasinya jauh lebih kompleks. Ini menggunakan berbagai fungsi yang diekspos oleh CLR.
GregRos
-2

Jika Anda memiliki lapisan cache diimplementasikan dengan C # itu jauh lebih baik juga meletakkan data Anda dalam cache sebagai referensi lemah, itu bisa membantu untuk meningkatkan kinerja lapisan cache Anda.

Pikirkan bahwa pendekatan juga dapat diterapkan pada implementasi sesi. Karena sesi adalah objek yang panjang, sebagian besar waktu, bisa jadi kasus ketika Anda tidak memiliki memori untuk pengguna baru. Dalam hal ini akan jauh lebih baik untuk menghapus beberapa objek sesi pengguna lain kemudian membuang OutOfMemoryException.

Juga, jika Anda memiliki objek besar dalam aplikasi Anda (beberapa tabel pencarian besar, dll), yang harus digunakan sangat jarang dan membuat ulang objek seperti itu bukanlah prosedur yang sangat mahal. Maka lebih baik memilikinya seperti referensi seminggu untuk memiliki cara untuk membebaskan memori Anda ketika Anda benar-benar membutuhkannya.

Ph0en1x
sumber
5
tetapi masalah dengan referensi yang lemah (lihat jawaban yang saya rujuk) adalah bahwa mereka sangat bersemangat dikumpulkan, dan koleksi tidak terkait dengan ketersediaan ruang memori. Jadi Anda berakhir dengan lebih banyak cache yang hilang ketika tidak ada tekanan pada memori.
1
Tetapi untuk poin kedua Anda tentang objek besar, dokumen MSDN menyatakan bahwa sementara referensi yang lemah dan panjang memungkinkan Anda membuat ulang objek, statusnya tetap tidak dapat diprediksi. Jika Anda akan membuatnya dari awal setiap kali, mengapa repot-repot menggunakan referensi yang lemah ketika Anda bisa memanggil fungsi / metode untuk membuatnya sesuai permintaan dan mengembalikan contoh sementara?
Ada satu situasi di mana caching sangat membantu: Jika seseorang akan sering membuat objek yang tidak dapat diubah, banyak di antaranya akan identik (misalnya membaca banyak baris dari file yang diharapkan memiliki banyak duplikat) setiap string akan dibuat sebagai objek baru , tetapi jika sebuah baris cocok dengan baris lain yang sudah ada referensi, efisiensi memori dapat ditingkatkan jika instance baru ditinggalkan dan referensi ke instance yang sudah ada diganti. Perhatikan bahwa substitusi ini berguna karena referensi lain tetap dipegang. Jika bukan kode harus menyimpan yang baru.
supercat