Saya baru saja menyadari, dengan membaca beberapa pertanyaan dan jawaban di StackOverflow, bahwa menambahkan pengendali event menggunakan +=
C # (atau saya kira, bahasa .net lainnya) dapat menyebabkan kebocoran memori umum ...
Saya telah menggunakan event handler seperti ini di masa lalu berkali-kali, dan tidak pernah menyadari bahwa mereka dapat menyebabkan, atau menyebabkan, kebocoran memori pada aplikasi saya.
Bagaimana cara kerjanya (artinya, mengapa ini sebenarnya menyebabkan kebocoran memori)?
Bagaimana saya bisa memperbaiki masalah ini? Apakah cukup menggunakan -=
event handler yang sama?
Apakah ada pola desain umum atau praktik terbaik untuk menangani situasi seperti ini?
Contoh: Bagaimana saya seharusnya menangani aplikasi yang memiliki banyak utas berbeda, menggunakan banyak penangan acara yang berbeda untuk mengangkat beberapa acara di UI?
Apakah ada cara yang baik dan sederhana untuk memonitor ini secara efisien dalam aplikasi besar yang sudah dibangun?
Ya,
-=
sudah cukup, Namun, bisa sangat sulit untuk melacak setiap acara yang ditugaskan, selamanya. (untuk detail, lihat posting Jon). Mengenai pola desain, lihat pola acara yang lemah .sumber
IDisposable
dan berhenti berlangganan dari acara.Saya telah menjelaskan kebingungan ini dalam sebuah blog di https://www.spicelogic.com/Blog/net-event-handler-memory-leak-16 . Saya akan mencoba merangkumnya di sini sehingga Anda dapat memiliki gagasan yang jelas.
Referensi berarti, "Perlu":
Pertama-tama, Anda perlu memahami bahwa, jika objek A memiliki referensi ke objek B, maka, itu berarti, objek A membutuhkan objek B agar berfungsi, bukan? Jadi, pengumpul sampah tidak akan mengumpulkan objek B selama objek A masih hidup dalam memori.
Saya pikir bagian ini harus jelas bagi pengembang.
+ = Berarti, menyuntikkan referensi objek sisi kanan ke objek kiri:
Tetapi, kebingungan berasal dari operator C # + =. Operator ini tidak dengan jelas memberi tahu pengembang bahwa, sisi kanan operator ini sebenarnya menyuntikkan referensi ke objek sisi kiri.
Dan dengan melakukan itu, objek A berpikir, perlu objek B, meskipun, dari sudut pandang Anda, objek A tidak peduli jika objek B hidup atau tidak. Karena objek A menganggap objek B diperlukan, objek A melindungi objek B dari pengumpul sampah selama objek A masih hidup. Tetapi, jika Anda tidak ingin perlindungan diberikan kepada objek pelanggan acara, maka, Anda dapat mengatakan, kebocoran memori terjadi.
Anda dapat menghindari kebocoran seperti itu dengan melepaskan event handler.
Bagaimana cara mengambil keputusan?
Tapi, ada banyak acara dan penangan acara di seluruh basis kode Anda. Apakah itu berarti, Anda harus terus melepaskan penangan acara di mana-mana? Jawabannya adalah Tidak. Jika Anda harus melakukannya, basis kode Anda akan sangat jelek dengan verbose.
Anda bisa mengikuti bagan alur sederhana untuk menentukan apakah penangan kejadian yang memisahkan diperlukan atau tidak.
Sebagian besar waktu, Anda mungkin menemukan objek acara pelanggan sama pentingnya dengan objek penerbit acara dan keduanya seharusnya hidup pada waktu yang sama.
Contoh skenario di mana Anda tidak perlu khawatir
Misalnya, acara klik tombol dari sebuah jendela.
Di sini, penerbit acara adalah Tombol, dan pelanggan acara adalah MainWindow. Menerapkan bagan alur itu, ajukan pertanyaan, apakah Jendela Utama (pelanggan acara) seharusnya sudah mati sebelum Tombol (penerbit acara)? Jelas Tidak. Benar? Itu bahkan tidak masuk akal. Lalu, mengapa khawatir tentang melepaskan event handler klik?
Contoh ketika pelepasan event handler adalah HARUS.
Saya akan memberikan satu contoh di mana objek pelanggan seharusnya sudah mati sebelum objek penerbit. Katakan, MainWindow Anda menerbitkan acara yang bernama "SomethingHappened" dan Anda menampilkan jendela anak dari jendela utama dengan mengklik tombol. Jendela anak berlangganan acara jendela utama itu.
Dan, jendela anak berlangganan acara Jendela Utama.
Dari kode ini, kita dapat dengan jelas memahami bahwa ada tombol di Jendela Utama. Mengklik tombol itu menunjukkan Window Anak. Jendela anak mendengarkan acara dari jendela utama. Setelah melakukan sesuatu, pengguna menutup jendela anak.
Sekarang, sesuai dengan bagan alur yang saya berikan jika Anda mengajukan pertanyaan "Apakah jendela anak (pelanggan acara) seharusnya sudah mati sebelum penerbit acara (jendela utama)? Jawabannya harus YA. Benar? Jadi, lepaskan penyelenggara acara Saya biasanya melakukan itu dari event Window yang tidak diturunkan.
Aturan praktis: Jika tampilan Anda (yaitu WPF, WinForm, UWP, Formulir Xamarin, dll.) Berlangganan ke acara ViewModel, selalu ingat untuk melepaskan pengendali acara. Karena ViewModel biasanya hidup lebih lama daripada tampilan. Jadi, jika ViewModel tidak dihancurkan, setiap tampilan yang berlangganan acara dari ViewModel itu akan tetap tersimpan dalam memori, yang tidak baik.
Bukti konsep menggunakan memory profiler.
Tidak akan terlalu menyenangkan jika kita tidak dapat memvalidasi konsep dengan profiler memori. Saya telah menggunakan JetBrain dotMemory profiler dalam percobaan ini.
Pertama, saya telah menjalankan MainWindow, yang muncul seperti ini:
Kemudian, saya mengambil snapshot memori. Lalu saya mengklik tombol 3 kali . Tiga jendela anak muncul. Saya telah menutup semua jendela anak dan mengklik tombol Force GC di profiler dotMemory untuk memastikan bahwa Pengumpul Sampah dipanggil. Kemudian, saya mengambil snapshot memori lain dan membandingkannya. Melihat! Ketakutan kami benar. Jendela Anak tidak dikumpulkan oleh pengumpul Sampah bahkan setelah mereka ditutup. Tidak hanya itu, tetapi jumlah objek bocor untuk objek ChildWindow juga ditampilkan " 3 " (Saya mengklik tombol 3 kali untuk menampilkan 3 jendela anak).
Ok, kalau begitu, saya melepaskan event handler seperti yang ditunjukkan di bawah ini.
Kemudian, saya telah melakukan langkah yang sama dan memeriksa memori profiler. Kali ini, wow! tidak ada lagi kebocoran memori.
sumber
Suatu acara adalah benar-benar daftar tertaut dari penangan acara
Ketika Anda melakukan + = EventHandler baru pada acara tersebut, tidak masalah jika fungsi khusus ini telah ditambahkan sebagai pendengar sebelumnya, itu akan ditambahkan sekali per + =.
Ketika acara dinaikkan itu melalui daftar yang ditautkan, item demi item dan memanggil semua metode (event handler) yang ditambahkan ke daftar ini, inilah mengapa penangan event masih dipanggil bahkan ketika halaman tidak lagi berjalan selama mereka hidup (berakar), dan mereka akan hidup selama mereka terhubung. Jadi mereka akan dipanggil sampai eventhandler tidak terkait dengan EventHandler - = baru.
Lihat disini
dan MSDN DI SINI
sumber