MEMPERBARUI
Mulai dari C # 6, jawaban untuk pertanyaan ini adalah:
SomeEvent?.Invoke(this, e);
Saya sering mendengar / membaca saran berikut:
Selalu buat salinan acara sebelum Anda memeriksanya null
dan memecatnya. Ini akan menghilangkan potensi masalah dengan threading di mana acara tersebut terjadinull
berada di lokasi yang tepat di antara tempat Anda memeriksa nol dan tempat Anda memecat acara:
// Copy the event delegate before checking/calling
EventHandler copy = TheEvent;
if (copy != null)
copy(this, EventArgs.Empty); // Call any handlers on the copied list
Diperbarui : Saya berpikir dari membaca tentang optimasi bahwa ini mungkin juga mengharuskan anggota acara menjadi tidak stabil, tetapi Jon Skeet menyatakan dalam jawabannya bahwa CLR tidak mengoptimalkan salinan.
Tetapi sementara itu, agar masalah ini terjadi, utas lainnya pasti telah melakukan sesuatu seperti ini:
// Better delist from event - don't want our handler called from now on:
otherObject.TheEvent -= OnTheEvent;
// Good, now we can be certain that OnTheEvent will not run...
Urutan sebenarnya mungkin campuran ini:
// Copy the event delegate before checking/calling
EventHandler copy = TheEvent;
// Better delist from event - don't want our handler called from now on:
otherObject.TheEvent -= OnTheEvent;
// Good, now we can be certain that OnTheEvent will not run...
if (copy != null)
copy(this, EventArgs.Empty); // Call any handlers on the copied list
Intinya adalah itu OnTheEvent
berjalan setelah penulis telah berhenti berlangganan, namun mereka hanya berhenti berlangganan khusus untuk menghindari hal itu terjadi. Tentunya yang benar-benar dibutuhkan adalah implementasi acara kustom dengan sinkronisasi yang sesuai di add
dan remove
accessor. Dan di samping itu ada masalah kemungkinan kebuntuan jika kunci ditahan saat suatu peristiwa dipecat.
Begitu juga Pemrograman Kargo Kargo ini ? Sepertinya begitu - banyak orang harus mengambil langkah ini untuk melindungi kode mereka dari banyak utas, padahal dalam kenyataannya menurut saya peristiwa memerlukan jauh lebih banyak perhatian daripada ini sebelum mereka dapat digunakan sebagai bagian dari desain multi-utas . Akibatnya, orang-orang yang tidak mengambil perawatan tambahan mungkin juga mengabaikan saran ini - itu bukan masalah untuk program single-threaded, dan pada kenyataannya, mengingat tidak adanya volatile
sebagian besar kode contoh online, saran tersebut mungkin tidak memiliki efek sama sekali.
(Dan bukankah jauh lebih mudah untuk hanya menetapkan yang kosong delegate { }
pada deklarasi anggota sehingga Anda tidak perlu memeriksa null
sejak awal?)
Diperbarui:Jika tidak jelas, saya memahami maksud dari saran - untuk menghindari pengecualian referensi nol dalam semua keadaan. Maksud saya adalah pengecualian referensi nol khusus ini hanya dapat terjadi jika utas lain menghapus daftar acara, dan satu-satunya alasan untuk melakukan itu adalah untuk memastikan bahwa tidak ada panggilan lebih lanjut akan diterima melalui peristiwa itu, yang jelas TIDAK BISA dicapai dengan teknik ini . Anda akan menyembunyikan kondisi balapan - akan lebih baik untuk mengungkapkannya! Pengecualian nol itu membantu mendeteksi penyalahgunaan komponen Anda. Jika Anda ingin komponen Anda dilindungi dari penyalahgunaan, Anda dapat mengikuti contoh WPF - simpan ID utas di konstruktor Anda dan kemudian berikan pengecualian jika utas lain mencoba berinteraksi langsung dengan komponen Anda. Atau menerapkan komponen yang benar-benar aman (bukan tugas yang mudah).
Jadi saya berpendapat bahwa hanya melakukan ini copy / cek idiom adalah pemrograman kargo, menambahkan kekacauan dan kebisingan ke kode Anda. Untuk benar-benar melindungi dari utas lainnya membutuhkan lebih banyak pekerjaan.
Pembaruan dalam menanggapi posting blog Eric Lippert:
Jadi ada hal utama yang saya lewatkan tentang penangan acara: "penangan acara harus kuat dalam menghadapi dipanggil bahkan setelah acara telah berhenti berlangganan", dan jelas karena itu kita hanya perlu peduli tentang kemungkinan acara mendelegasikan keberadaan null
. Apakah persyaratan pada penangan acara didokumentasikan di mana saja?
Maka: "Ada cara lain untuk menyelesaikan masalah ini; misalnya, menginisialisasi pawang untuk memiliki tindakan kosong yang tidak pernah dihapus. Tetapi melakukan pemeriksaan nol adalah pola standar."
Jadi satu fragmen yang tersisa dari pertanyaan saya adalah, mengapa eksplisit-nol-periksa "pola standar"? Alternatif, menugaskan delegasi kosong, hanya = delegate {}
perlu ditambahkan ke deklarasi acara, dan ini menghilangkan tumpukan-tumpukan kecil upacara bau dari setiap tempat di mana acara dibesarkan. Akan mudah untuk memastikan bahwa delegasi yang kosong murah untuk instantiate. Atau apakah saya masih melewatkan sesuatu?
Pasti itu (seperti yang disarankan Jon Skeet) ini hanya .NET 1.x saran yang belum padam, seperti yang seharusnya dilakukan pada tahun 2005?
sumber
EventName(arguments)
memanggil delegasi acara tanpa syarat, daripada meminta delegasi acara hanya jika tidak nol (tidak melakukan apa pun jika nol).Jawaban:
JIT tidak diperbolehkan untuk melakukan optimasi yang Anda bicarakan di bagian pertama, karena kondisi tersebut. Saya tahu ini dibesarkan sebagai momok beberapa waktu lalu, tetapi itu tidak valid. (Aku memeriksanya dengan Joe Duffy atau Vance Morrison beberapa waktu yang lalu; Aku tidak ingat yang mana.)
Tanpa pengubah volatil itu mungkin bahwa salinan lokal yang diambil akan ketinggalan zaman, tapi itu saja. Itu tidak akan menyebabkan a
NullReferenceException
.Dan ya, tentu saja ada kondisi balapan - tetapi akan selalu ada. Misalkan kita hanya mengubah kode menjadi:
Sekarang anggaplah bahwa daftar doa untuk delegasi itu memiliki 1000 entri. Sangat mungkin bahwa tindakan di awal daftar akan dieksekusi sebelum utas lain berhenti berlangganan handler di dekat akhir daftar. Namun, pawang itu masih akan dieksekusi karena itu akan menjadi daftar baru. (Delegasi tidak bisa berubah.) Sejauh yang saya bisa lihat, ini tidak bisa dihindari.
Menggunakan delegasi kosong tentu saja menghindari cek nullity, tetapi tidak memperbaiki kondisi balapan. Ini juga tidak menjamin bahwa Anda selalu "melihat" nilai terbaru dari variabel.
sumber
Saya melihat banyak orang pergi ke arah metode perpanjangan melakukan ini ...
Itu memberi Anda sintaksis yang lebih baik untuk meningkatkan acara ...
Dan juga menghilangkan salinan lokal karena ditangkap pada waktu panggilan metode.
sumber
"Mengapa secara eksplisit-nol-periksa 'pola standar'?"
Saya menduga alasan untuk ini mungkin karena cek-nol lebih banyak performan.
Jika Anda selalu berlangganan delegasi kosong ke acara Anda saat dibuat, akan ada beberapa biaya tambahan:
(Perhatikan bahwa kontrol UI sering memiliki sejumlah besar acara, yang sebagian besar tidak pernah dilanggan. Harus membuat pelanggan dummy untuk setiap peristiwa dan kemudian memohonnya kemungkinan akan menjadi hit kinerja yang signifikan.)
Saya melakukan beberapa pengujian kinerja sepintas untuk melihat dampak dari pendekatan delegasi-kosong-langganan, dan berikut ini adalah hasil saya:
Perhatikan bahwa untuk kasus nol atau satu pelanggan (umum untuk kontrol UI, di mana banyak peristiwa), acara yang diinisialisasi dengan delegasi kosong terutama lebih lambat (lebih dari 50 juta iterasi ...)
Untuk informasi lebih lanjut dan kode sumber, kunjungi posting blog ini di . Utas utas acara NET yang saya publikasikan sehari sebelum pertanyaan ini ditanyakan (!)
(Pengaturan pengujian saya mungkin salah, jadi silakan mengunduh kode sumber dan memeriksanya sendiri. Umpan balik sangat dihargai.)
sumber
Delegate.Combine
/Delegate.Remove
pasangan dalam kasus di mana acara tersebut memiliki nol, satu, atau dua pelanggan; jika seseorang berulang kali menambah dan menghapus instance delegasi yang sama, perbedaan biaya di antara kasus-kasus akan secara khusus diucapkan karenaCombine
memiliki perilaku kasus khusus cepat ketika salah satu argumennull
(hanya mengembalikan yang lain), danRemove
sangat cepat ketika kedua argumen tersebut sama dengan (hanya mengembalikan nol).Saya benar-benar menikmati bacaan ini - bukan! Meskipun saya membutuhkannya untuk bekerja dengan fitur yang disebut fitur C #!
Mengapa tidak memperbaiki ini di kompiler? Saya tahu ada MS orang yang membaca posting ini, jadi tolong jangan nyalakan ini!
1 - masalah Null ) Mengapa tidak membuat acara menjadi .Empty bukannya null di tempat pertama? Berapa banyak baris kode yang akan disimpan untuk pemeriksaan nol atau harus tetap
= delegate {}
pada deklarasi? Biarkan kompilator menangani kasus Kosong, yaitu IE tidak melakukan apa pun! Jika semuanya penting bagi pembuat acara, mereka dapat memeriksa .Empty dan melakukan apa pun yang mereka pedulikan! Kalau tidak, semua null check / delegate add adalah hacks di sekitar masalahnya!Jujur saya lelah harus melakukan ini dengan setiap peristiwa - alias kode boilerplate!
2 - masalah kondisi balapan ) Saya membaca posting blog Eric, saya setuju bahwa H (handler) harus menangani ketika dereferensi itu sendiri, tetapi tidak bisakah acara tersebut dibuat abadi / aman? IE, mengatur bendera kunci pada pembuatannya, sehingga setiap kali dipanggil, ia mengunci semua berlangganan dan berhenti berlangganan saat menjalankannya?
Kesimpulan ,
Bukankah bahasa modern seharusnya memecahkan masalah seperti ini untuk kita?
sumber
Dengan C # 6 ke atas, kode dapat disederhanakan menggunakan
?.
operator baru seperti pada:TheEvent?.Invoke(this, EventArgs.Empty);
Berikut ini dokumentasi MSDN.
sumber
Menurut Jeffrey Richter dalam buku CLR via C # , metode yang benar adalah:
Karena itu memaksa salinan referensi. Untuk informasi lebih lanjut, lihat bagian Acara di buku.
sumber
Interlocked.CompareExchange
akan gagal jika entah bagaimana dilewatkan nolref
, tetapi itu tidak sama dengan diteruskanref
ke lokasi penyimpanan (misalnyaNewMail
) yang ada dan yang awalnya memegang referensi nol.Saya telah menggunakan pola desain ini untuk memastikan bahwa event handler tidak dieksekusi setelah mereka berhenti berlangganan. Sejauh ini sudah berfungsi dengan baik, meskipun saya belum mencoba profil kinerja apa pun.
Saya sebagian besar bekerja dengan Mono untuk Android hari ini, dan Android sepertinya tidak menyukainya ketika Anda mencoba memperbarui Tampilan setelah Aktivitasnya dikirim ke latar belakang.
sumber
Praktik ini bukan tentang menegakkan urutan operasi tertentu. Ini sebenarnya tentang menghindari pengecualian referensi nol.
Alasan di balik orang yang peduli tentang pengecualian referensi nol dan bukan kondisi ras akan membutuhkan penelitian psikologis yang mendalam. Saya pikir itu ada hubungannya dengan fakta bahwa memperbaiki masalah referensi nol jauh lebih mudah. Setelah itu diperbaiki, mereka menggantung spanduk besar "Mission Accomplished" pada kode mereka dan membuka ritsleting setelan penerbangan mereka.
Catatan: memperbaiki kondisi balapan mungkin melibatkan menggunakan trek bendera sinkron apakah pawang harus berjalan
sumber
Unload
acara) untuk secara aman membatalkan langganan suatu objek ke acara lain. Menjijikan. Lebih baik adalah dengan mengatakan bahwa permintaan berhenti berlangganan acara akan menyebabkan acara berhenti berlangganan "pada akhirnya", dan bahwa pelanggan acara harus memeriksa kapan mereka dipanggil apakah ada sesuatu yang berguna untuk mereka lakukan.Jadi saya agak terlambat ke pesta di sini. :)
Adapun penggunaan null daripada pola objek nol untuk mewakili peristiwa tanpa pelanggan, pertimbangkan skenario ini. Anda perlu memohon suatu peristiwa, tetapi membuat objek (EventArgs) adalah non-sepele, dan dalam kasus umum acara Anda tidak memiliki pelanggan. Akan bermanfaat bagi Anda jika Anda dapat mengoptimalkan kode Anda untuk memeriksa untuk melihat apakah Anda memiliki pelanggan sama sekali sebelum Anda melakukan upaya pemrosesan untuk membangun argumen dan menjalankan acara.
Dengan pemikiran ini, solusinya adalah dengan mengatakan "baik, nol pelanggan diwakili oleh nol." Kemudian cukup lakukan pemeriksaan nol sebelum melakukan operasi mahal Anda. Saya kira cara lain untuk melakukan ini adalah dengan memiliki properti Count pada tipe Delegate, jadi Anda hanya akan melakukan operasi yang mahal jika myDelegate.Count> 0. Menggunakan properti Count adalah pola yang agak bagus yang menyelesaikan masalah asli memungkinkan pengoptimalan, dan juga memiliki properti bagus untuk dapat dipanggil tanpa menyebabkan NullReferenceException.
Perlu diingat, bahwa karena delegasi adalah tipe referensi, mereka diijinkan nol. Mungkin tidak ada cara yang baik untuk menyembunyikan fakta ini di bawah selimut dan hanya mendukung pola objek nol untuk acara, jadi alternatifnya mungkin memaksa pengembang untuk memeriksa baik nol dan nol pelanggan. Itu akan lebih jelek dari situasi saat ini.
Catatan: Ini murni spekulasi. Saya tidak terlibat dengan bahasa .NET atau CLR.
sumber
+=
dan-=
. Di dalam kelas, operasi yang diizinkan juga akan mencakup pemanggilan (dengan pemeriksaan nol bawaan), pengujian terhadapnull
, atau pengaturan kenull
. Untuk hal lain, seseorang harus menggunakan delegasi yang namanya akan menjadi nama acara dengan beberapa awalan atau akhiran tertentu.untuk aplikasi threaded tunggal, Anda benar ini bukan masalah.
Namun, jika Anda membuat komponen yang mengekspos peristiwa, tidak ada jaminan bahwa konsumen komponen Anda tidak akan melakukan multithreading, dalam hal ini Anda harus bersiap untuk yang terburuk.
Menggunakan delegasi kosong tidak menyelesaikan masalah, tetapi juga menyebabkan kinerja hit pada setiap panggilan ke acara tersebut, dan mungkin bisa memiliki implikasi GC.
Anda benar bahwa konsumen tidak berhenti berlangganan agar hal ini terjadi, tetapi jika mereka berhasil melewati salinan temp, maka pertimbangkan pesan yang sudah dalam perjalanan.
Jika Anda tidak menggunakan variabel sementara, dan tidak menggunakan delegasi kosong, dan seseorang berhenti berlangganan, Anda mendapatkan pengecualian referensi nol, yang fatal, jadi saya pikir biayanya sepadan.
sumber
Saya tidak pernah benar-benar menganggap ini sebagai masalah karena saya umumnya hanya melindungi terhadap potensi kejahatan threading semacam ini dalam metode statis (dll) pada komponen yang dapat digunakan kembali, dan saya tidak membuat acara statis.
Apakah saya salah?
sumber
Kirim semua acara Anda di tempat konstruksi dan biarkan saja. Desain kelas Delegasi tidak mungkin menangani penggunaan lainnya dengan benar, seperti yang akan saya jelaskan pada paragraf terakhir dari posting ini.
Pertama-tama, tidak ada gunanya mencoba mencegat pemberitahuan acara ketika penangan acara Anda harus sudah membuat keputusan tersinkronisasi tentang apakah / bagaimana menanggapi pemberitahuan tersebut .
Apa pun yang dapat diberitahukan, harus diberitahukan. Jika penangan acara Anda menangani pemberitahuan dengan benar (yaitu mereka memiliki akses ke negara aplikasi yang berwenang dan merespons hanya jika perlu), maka akan baik untuk memberi tahu mereka kapan saja dan percaya mereka akan merespons dengan baik.
Satu-satunya saat seorang pawang tidak diberi tahu bahwa suatu peristiwa telah terjadi, adalah jika peristiwa tersebut tidak terjadi! Jadi, jika Anda tidak ingin pawang diberitahu, berhentilah membuat acara (mis. Nonaktifkan kontrol atau apa pun yang bertanggung jawab untuk mendeteksi dan menjadikan acara itu ada sejak awal).
Sejujurnya, saya pikir kelas Delegasi tidak dapat diselamatkan. Penggabungan / transisi ke MulticastDelegate adalah kesalahan besar, karena secara efektif mengubah definisi (berguna) dari suatu peristiwa dari sesuatu yang terjadi pada satu waktu, menjadi sesuatu yang terjadi dalam rentang waktu tertentu. Perubahan semacam itu membutuhkan mekanisme sinkronisasi yang secara logis dapat menciutkannya kembali menjadi satu instan, tetapi MulticastDelegate tidak memiliki mekanisme semacam itu. Sinkronisasi harus mencakup seluruh rentang waktu atau instan acara berlangsung, sehingga setelah aplikasi membuat keputusan yang disinkronkan untuk mulai menangani suatu acara, ia selesai menangani sepenuhnya (secara transaksi). Dengan kotak hitam yang merupakan kelas hybrid MulticastDelegate / Delegate, ini hampir mustahil, jadimematuhi penggunaan pelanggan tunggal dan / atau mengimplementasikan MulticastDelegate jenis Anda sendiri yang memiliki pegangan sinkronisasi yang dapat diambil saat rantai handler sedang digunakan / dimodifikasi . Saya merekomendasikan ini, karena alternatifnya adalah mengimplementasikan sinkronisasi / transaksional-integritas secara berlebihan di semua handler Anda, yang akan menjadi rumit / tidak perlu rumit.
sumber
Silakan lihat di sini: http://www.danielfortunov.com/software/%24daniel_fortunovs_adventures_in_software_development/2009/04/23/net_event_invocation_thread_safety Ini adalah solusi yang tepat dan harus selalu digunakan sebagai pengganti semua solusi lainnya.
“Anda dapat memastikan bahwa daftar doa internal selalu memiliki setidaknya satu anggota dengan menginisialisasi dengan metode anonim apa-apa. Karena tidak ada pihak eksternal dapat memiliki referensi ke metode anonim, tidak ada pihak eksternal dapat menghapus metode, sehingga delegasi tidak akan pernah nol ”- Pemrograman. Komponen NET, Edisi 2, oleh Juval Löwy
sumber
Saya tidak percaya pertanyaannya terbatas pada tipe c # "event". Menghapus batasan itu, mengapa tidak menciptakan kembali roda sedikit dan melakukan sesuatu di sepanjang garis ini?
Naikkan utas acara dengan aman - praktik terbaik
sumber
Terima kasih atas diskusi yang bermanfaat. Saya sedang mengerjakan masalah ini baru-baru ini dan membuat kelas berikut yang sedikit lebih lambat, tetapi memungkinkan untuk menghindari panggilan ke objek yang dibuang.
Poin utama di sini adalah bahwa daftar doa dapat dimodifikasi bahkan acara dimunculkan.
Dan penggunaannya adalah:
Uji
Saya mengujinya dengan cara berikut. Saya memiliki utas yang membuat dan menghancurkan objek seperti ini:
Dalam
Bar
konstruktor (objek pendengar) saya berlanggananSomeEvent
(yang diterapkan seperti yang ditunjukkan di atas) dan berhenti berlangganan diDispose
:Juga saya punya beberapa utas yang meningkatkan acara dalam satu lingkaran.
Semua tindakan ini dilakukan secara bersamaan: banyak pendengar diciptakan dan dihancurkan dan acara dipecat pada saat yang sama.
Jika ada kondisi lomba saya harus melihat pesan di konsol, tetapi kosong. Tetapi jika saya menggunakan acara CLR seperti biasa saya melihatnya penuh dengan pesan peringatan. Jadi, saya dapat menyimpulkan bahwa adalah mungkin untuk mengimplementasikan acara yang aman untuk thread di c #.
Bagaimana menurut anda?
sumber
disposed = true
terjadi sebelumfoo.SomeEvent -= Handler
dalam aplikasi pengujian Anda, menghasilkan false positive. Namun terlepas dari itu, ada beberapa hal yang mungkin ingin Anda ubah. Anda benar-benar ingin menggunakantry ... finally
untuk kunci - ini akan membantu Anda membuat ini tidak hanya aman, tetapi juga aman dibatalkan. Belum lagi Anda bisa menyingkirkan itu konyoltry catch
. Dan Anda tidak memeriksa delegasi yang lewatAdd
/Remove
- bisa jadinull
(Anda harus langsung membuangnya diAdd
/Remove
).