Saya tidak bisa sampai ke bagian bawah kesalahan ini, karena ketika debugger terpasang, sepertinya tidak terjadi. Di bawah ini adalah kode.
Ini adalah server WCF dalam layanan Windows. Metode NotifySubscribers dipanggil oleh layanan setiap kali ada acara data (secara acak, tetapi tidak terlalu sering - sekitar 800 kali per hari).
Ketika klien Windows Forms berlangganan, ID pelanggan ditambahkan ke kamus pelanggan, dan ketika klien berhenti berlangganan, itu dihapus dari kamus. Kesalahan terjadi ketika (atau setelah) klien berhenti berlangganan. Tampaknya kali berikutnya metode NotifySubscribers () dipanggil, loop foreach () gagal dengan kesalahan di baris subjek. Metode menulis kesalahan ke dalam log aplikasi seperti yang ditunjukkan dalam kode di bawah ini. Ketika debugger dilampirkan dan klien berhenti berlangganan, kode dieksekusi dengan baik.
Apakah Anda melihat masalah dengan kode ini? Apakah saya perlu membuat kamus thread-safe?
[ServiceBehavior(InstanceContextMode=InstanceContextMode.Single)]
public class SubscriptionServer : ISubscriptionServer
{
private static IDictionary<Guid, Subscriber> subscribers;
public SubscriptionServer()
{
subscribers = new Dictionary<Guid, Subscriber>();
}
public void NotifySubscribers(DataRecord sr)
{
foreach(Subscriber s in subscribers.Values)
{
try
{
s.Callback.SignalData(sr);
}
catch (Exception e)
{
DCS.WriteToApplicationLog(e.Message,
System.Diagnostics.EventLogEntryType.Error);
UnsubscribeEvent(s.ClientId);
}
}
}
public Guid SubscribeEvent(string clientDescription)
{
Subscriber subscriber = new Subscriber();
subscriber.Callback = OperationContext.Current.
GetCallbackChannel<IDCSCallback>();
subscribers.Add(subscriber.ClientId, subscriber);
return subscriber.ClientId;
}
public void UnsubscribeEvent(Guid clientId)
{
try
{
subscribers.Remove(clientId);
}
catch(Exception e)
{
System.Diagnostics.Debug.WriteLine("Unsubscribe Error " +
e.Message);
}
}
}
sumber
Jawaban:
Apa yang mungkin terjadi adalah bahwa SignalData secara tidak langsung mengubah kamus pelanggan di bawah tenda selama loop dan mengarah ke pesan itu. Anda dapat memverifikasi ini dengan mengubah
Untuk
Jika saya benar, masalahnya akan hilang
Memanggil
subscribers.Values.ToList()
menyalin nilai-nilaisubscribers.Values
ke daftar terpisah di awalforeach
. Tidak ada lagi yang memiliki akses ke daftar ini (bahkan tidak memiliki nama variabel!), Jadi tidak ada yang dapat memodifikasinya di dalam loop.sumber
subscribers.Values
adalah sedang dimodifikasi di dalamforeach
loop. Memanggilsubscribers.Values.ToList()
menyalin nilai-nilaisubscribers.Values
ke daftar terpisah di awalforeach
. Tidak ada lagi yang memiliki akses ke daftar ini (bahkan tidak memiliki nama variabel!) , Jadi tidak ada yang dapat memodifikasinya di dalam loop.ToList
juga bisa melempar jika koleksi dimodifikasi saatToList
sedang dieksekusi.ToList
bukan operasi atom. Apa yang lebih lucu, padaToList
dasarnya melakukan sendiri secaraforeach
internal untuk menyalin item ke instance daftar baru, yang berarti Anda memperbaikiforeach
masalah dengan menambahkanforeach
iterasi tambahan (meskipun lebih cepat) .Ketika pelanggan berhenti berlangganan, Anda mengubah konten koleksi Pelanggan selama penghitungan.
Ada beberapa cara untuk memperbaikinya, salah satunya adalah mengubah for for menggunakan eksplisit
.ToList()
:sumber
Cara yang lebih efisien, menurut saya, adalah memiliki daftar lain yang Anda nyatakan bahwa Anda memasukkan apa pun yang "harus dihapus". Kemudian setelah Anda menyelesaikan loop utama Anda (tanpa .ToList ()), Anda melakukan loop lain pada daftar "yang akan dihapus", menghapus setiap entri saat itu terjadi. Jadi di kelas Anda, Anda menambahkan:
Kemudian Anda mengubahnya menjadi:
Ini tidak hanya akan menyelesaikan masalah Anda, itu akan mencegah Anda dari harus terus membuat daftar dari kamus Anda, yang mahal jika ada banyak pelanggan di sana. Dengan asumsi daftar pelanggan yang akan dihapus pada setiap iterasi yang diberikan lebih rendah dari jumlah total dalam daftar, ini harus lebih cepat. Tetapi tentu saja merasa bebas untuk membuat profil untuk memastikan bahwa itu masalahnya jika ada keraguan dalam situasi penggunaan khusus Anda.
sumber
Anda juga dapat mengunci kamus pelanggan Anda untuk mencegahnya diubah setiap kali di-looped:
sumber
MarkerFrequencies
kamus di dalam kunci itu sendiri, yang berarti bahwa instance asli tidak lagi terkunci. Coba juga menggunakanfor
bukanforeach
, Lihat ini dan ini . Beri tahu saya jika itu menyelesaikannya.System.Collections.Concurrent
namespace.Kenapa kesalahan ini?
Secara umum koleksi .Net tidak mendukung yang disebutkan dan dimodifikasi pada saat yang sama. Jika Anda mencoba mengubah daftar koleksi selama enumerasi, itu menimbulkan pengecualian. Jadi masalah di balik kesalahan ini adalah, kita tidak bisa mengubah daftar / kamus sementara kita mengulanginya.
Salah satu solusinya
Jika kita mengulang kamus menggunakan daftar kuncinya, secara paralel kita dapat memodifikasi objek kamus, karena kita mengulangi melalui koleksi kunci dan bukan kamus (dan mengulangi koleksi kuncinya).
Contoh
Berikut adalah posting blog tentang solusi ini.
Dan untuk menyelam lebih dalam di StackOverflow: Mengapa kesalahan ini terjadi?
sumber
Sebenarnya masalahnya bagi saya bahwa Anda menghapus elemen dari daftar dan berharap untuk terus membaca daftar seolah-olah tidak ada yang terjadi.
Apa yang benar-benar perlu Anda lakukan adalah mulai dari akhir dan kembali ke awal. Bahkan jika Anda menghapus elemen dari daftar, Anda akan dapat terus membacanya.
sumber
InvalidOperationException- Sebuah InvalidOperationException telah terjadi. Ini melaporkan "koleksi telah diubah" dalam foreach-loop
Gunakan pernyataan istirahat, Setelah objek dihapus.
ex:
sumber
Saya memiliki masalah yang sama, dan itu diselesaikan ketika saya menggunakan
for
loop, bukanforeach
.sumber
Oke jadi yang membantu saya adalah beralih ke belakang. Saya mencoba untuk menghapus entri dari daftar tetapi berulang ke atas dan mengacaukan loop karena entri tidak ada lagi:
sumber
Saya telah melihat banyak opsi untuk ini tetapi bagi saya yang satu ini adalah yang terbaik.
Maka cukup loop melalui koleksi.
Perlu diketahui bahwa ListItemCollection dapat berisi duplikat. Secara default tidak ada yang mencegah duplikat ditambahkan ke koleksi. Untuk menghindari duplikat Anda dapat melakukan ini:
sumber
Jawaban yang diterima tidak tepat dan salah dalam kasus terburuk. Jika perubahan dilakukan selama
ToList()
, Anda masih bisa berakhir dengan kesalahan. Selain itulock
, kinerja dan keamanan utas mana yang perlu dipertimbangkan jika Anda memiliki anggota publik, solusi yang tepat dapat menggunakan tipe yang tidak dapat diubah .Secara umum, jenis yang tidak dapat diubah berarti bahwa Anda tidak dapat mengubah statusnya setelah dibuat. Jadi kode Anda akan terlihat seperti:
Ini bisa sangat berguna jika Anda memiliki anggota publik. Kelas lain selalu bisa
foreach
pada tipe yang tidak berubah tanpa khawatir tentang koleksi yang sedang dimodifikasi.sumber
Berikut ini adalah skenario khusus yang memerlukan pendekatan khusus:
Dictionary
sering disebutkan.Dictionary
dimodifikasi jarang.Dalam skenario ini membuat salinan dari
Dictionary
(atauDictionary.Values
) sebelum setiap penghitungan bisa sangat mahal. Gagasan saya tentang mengatasi masalah ini adalah menggunakan kembali salinan cache yang sama dalam beberapa enumerasi, dan menonton yangIEnumerator
asliDictionary
sebagai pengecualian. Enumerator akan di-cache bersama dengan data yang disalin, dan diinterogasi sebelum memulai enumerasi baru. Dalam kasus pengecualian, salinan yang di-cache akan dibuang, dan yang baru akan dibuat. Inilah implementasi dari ide ini:Contoh penggunaan:
Sayangnya ide ini tidak dapat digunakan saat ini dengan kelas
Dictionary
di .NET Core 3.0, karena kelas ini tidak membuang Koleksi dimodifikasi pengecualian ketika disebutkan dan metodeRemove
danClear
dipanggil. Semua wadah lain yang saya periksa berperilaku konsisten. Aku memeriksa sistematis kelas ini:List<T>
,Collection<T>
,ObservableCollection<T>
,HashSet<T>
,SortedSet<T>
,Dictionary<T,V>
danSortedDictionary<T,V>
. Hanya dua metode yang disebutkan diDictionary
kelas di .NET Core tidak membatalkan enumerasi.Pembaruan: Saya memperbaiki masalah di atas dengan membandingkan juga panjang cache dan koleksi asli. Perbaikan ini mengasumsikan bahwa kamus akan diteruskan langsung sebagai argumen ke
EnumerableSnapshot
konstruktor, dan identitasnya tidak akan disembunyikan oleh (misalnya) proyeksi seperti:dictionary.Select(e => e).ΤοEnumerableSnapshot()
.Penting: Kelas di atas tidak aman untuk thread. Ini dimaksudkan untuk digunakan dari kode yang berjalan secara eksklusif dalam satu utas.
sumber
Anda bisa menyalin objek kamus pelanggan ke jenis yang sama dari objek kamus sementara dan kemudian iterate objek kamus sementara menggunakan foreach loop.
sumber
Jadi cara yang berbeda untuk menyelesaikan masalah ini adalah alih-alih menghapus elemen, buat kamus baru dan hanya tambahkan elemen yang tidak ingin Anda hapus lalu ganti kamus asli dengan yang baru. Saya tidak berpikir ini terlalu banyak masalah efisiensi karena tidak meningkatkan berapa kali Anda beralih pada struktur.
sumber
Ada satu tautan di mana itu diuraikan dengan sangat baik & solusi juga diberikan. Cobalah jika Anda mendapat solusi yang tepat, silakan kirim di sini sehingga orang lain dapat mengerti. Solusi yang diberikan ok maka seperti posting sehingga orang lain dapat mencoba solusi ini.
untuk Anda referensi tautan asli: - https://bensonxion.wordpress.com/2012/05/07/serializing-an-ienumerable-produces-collection-was-modified-enumeration-operation-may-not-execute/
Ketika kami menggunakan. Serialisasi kelas Net untuk membuat serial objek di mana definisinya berisi tipe Enumerable, yaitu koleksi, Anda akan dengan mudah mendapatkan InvalidOperationException yang mengatakan "Koleksi telah diubah; operasi penghitungan mungkin tidak dijalankan" di mana pengkodean Anda berada di bawah skenario multi-utas. Penyebab utamanya adalah bahwa kelas serialisasi akan beralih melalui pengumpulan melalui enumerator, sehingga masalah untuk mencoba beralih melalui koleksi sambil memodifikasinya.
Solusi pertama, kita cukup menggunakan kunci sebagai solusi sinkronisasi untuk memastikan bahwa operasi ke objek Daftar hanya dapat dijalankan dari satu utas pada satu waktu. Jelas, Anda akan mendapatkan penalti kinerja jika Anda ingin membuat serial koleksi objek itu, maka untuk masing-masing, kunci akan diterapkan.
Nah, .Net 4.0 yang membuat berurusan dengan skenario multi-threading berguna. untuk masalah bidang Pengumpulan serialisasi ini, saya menemukan kita bisa mengambil manfaat dari kelas ConcurrentQueue (Periksa MSDN), yang merupakan koleksi aman-benang dan FIFO dan membuat kode bebas dari kunci.
Menggunakan kelas ini, dalam kesederhanaannya, hal-hal yang perlu Anda modifikasi untuk kode Anda menggantikan tipe Koleksi dengannya, gunakan Enqueue untuk menambahkan elemen ke akhir ConcurrentQueue, hapus kode kunci itu. Atau, jika skenario yang sedang Anda kerjakan memang membutuhkan barang koleksi seperti Daftar, Anda akan memerlukan beberapa kode lagi untuk mengadaptasi ConcurrentQueue ke dalam bidang Anda.
BTW, ConcurrentQueue tidak memiliki metode yang jelas karena algoritma yang mendasari yang tidak mengizinkan pembersihan koleksi secara atom. jadi Anda harus melakukannya sendiri, cara tercepat adalah membuat kembali ConcurrentQueue kosong baru untuk pengganti.
sumber
Saya pribadi menjalankan ini baru-baru ini dalam kode asing di mana itu dalam dbcontext terbuka dengan Linq's .Remove (item). Saya mengalami kesulitan menemukan debugging kesalahan karena item dihapus pertama kali iterasi, dan jika saya mencoba untuk mundur dan melangkah kembali, koleksi itu kosong, karena saya berada dalam konteks yang sama!
sumber