Saya dapat't mendapatkan ke bagian bawah dari kesalahan ini, karena ketika debugger yang terpasang, hal ini tampaknya tidak terjadi. Di bawah ini adalah kode.
Ini adalah WCF server di Windows service. Metode NotifySubscribers disebut oleh layanan setiap kali ada data event (pada interval waktu yang acak, tetapi tidak sering - sekitar 800 kali per hari).
Ketika Windows Forms klien berlangganan, pelanggan ID ditambahkan ke pelanggan kamus, dan ketika klien berhenti berlangganan, maka akan dihapus dari kamus. Kesalahan terjadi ketika (atau setelah) klien berhenti berlangganan. Tampaknya bahwa waktu berikutnya NotifySubscribers() metode ini disebut, foreach() loop gagal dengan error di baris subjek. Metode menulis kesalahan dalam log aplikasi seperti yang ditunjukkan pada kode di bawah ini. Ketika debugger melekat dan klien berhenti berlangganan, kode mengeksekusi dengan baik.
Apakah anda melihat masalah dengan kode ini? Apakah saya perlu untuk 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);
}
}
}
Apa's mungkin terjadi adalah bahwa SignalData ini secara tidak langsung mengubah pelanggan kamus bawah kap selama loop dan terkemuka untuk pesan itu. Anda dapat memverifikasi ini dengan mengubah
foreach(Subscriber s in subscribers.Values)
Untuk
foreach(Subscriber s in subscribers.Values.ToList())
Jika saya'm tepat, masalah akan hilang
Menelepon pelanggan.Nilai-nilai.Kedaftar() menyalin nilai dari pelanggan.Nilai-nilai untuk sebuah daftar terpisah pada awal foreach. Tidak ada lagi yang memiliki akses ke daftar ini (itu doesn't bahkan memiliki variabel nama!), jadi tidak ada yang bisa mengubah itu di dalam lingkaran.
Bila pelanggan berhenti berlangganan anda mengubah isi dari koleksi dari Pelanggan selama pencacahan.
Ada beberapa cara untuk memperbaiki kesalahan ini, salah satu yang berubah untuk loop untuk menggunakan eksplisit .Kedaftar()
:
public void NotifySubscribers(DataRecord sr)
{
foreach(Subscriber s in subscribers.Values.ToList())
{
^^^^^^^^^
...
Cara yang lebih efisien, dalam pendapat saya, adalah untuk memiliki daftar lain bahwa anda menyatakan bahwa anda menempatkan segala sesuatu yang "untuk dihapus" ke. Kemudian setelah anda selesai main loop (tanpa .Kedaftar()), anda lakukan loop lain atas "untuk dihapus" daftar, menghapus setiap entri seperti itu terjadi. Jadi di kelas anda anda tambahkan:
private List<Guid> toBeRemoved = new List<Guid>();
Kemudian anda mengubah ke:
public void NotifySubscribers(DataRecord sr)
{
toBeRemoved.Clear();
...your unchanged code skipped...
foreach ( Guid clientId in toBeRemoved )
{
try
{
subscribers.Remove(clientId);
}
catch(Exception e)
{
System.Diagnostics.Debug.WriteLine("Unsubscribe Error " +
e.Message);
}
}
}
...your unchanged code skipped...
public void UnsubscribeEvent(Guid clientId)
{
toBeRemoved.Add( clientId );
}
Hal ini tidak hanya akan memecahkan masalah anda, itu akan mencegah anda dari keharusan untuk terus menciptakan sebuah daftar dari kamus anda, yang mahal jika ada banyak pelanggan di sana. Dengan asumsi daftar pelanggan yang akan dihapus pada setiap iterasi lebih rendah dari total jumlah dalam daftar, ini harus menjadi lebih cepat. Tapi tentu saja merasa bebas untuk profil ini untuk memastikan bahwa's terjadi jika ada's keraguan dalam penggunaan tertentu situasi.
//get key collection from dictionary into a list to loop through
List<int> keys = new List<int>(Dictionary.Keys);
// iterating key collection using a simple for-each loop
foreach (int key in keys)
{
// Now we can perform any modification with values of the dictionary.
Dictionary[key] = Dictionary[key] - 1;
}
Berikut ini adalah blog post tentang solusi ini.
Dan untuk menyelam jauh di StackOverflow: Mengapa kesalahan ini terjadi?
Sebenarnya masalah tampaknya bagi saya bahwa anda menghapus elemen dari daftar dan mengharapkan untuk terus membaca daftar seolah-olah tidak terjadi apa-apa.
Apa yang anda benar-benar perlu lakukan adalah untuk memulai dari akhir dan kembali ke awal. Bahkan jika anda menghapus elemen dari daftar, anda akan dapat untuk melanjutkan membacanya.
InvalidOperationException- InvalidOperationException telah terjadi. Ini laporan "koleksi diubah" dalam foreach loop
Menggunakan pernyataan break, Setelah objek dihapus.
ex:
ArrayList list = new ArrayList();
foreach (var item in list)
{
if(condition)
{
list.remove(item);
break;
}
}
Saya memiliki masalah yang sama, dan itu diselesaikan ketika saya menggunakan untuk
loop bukan foreach
.
// foreach (var item in itemsToBeLast)
for (int i = 0; i < itemsToBeLast.Count; i++)
{
var matchingItem = itemsToBeLast.FirstOrDefault(item => item.Detach);
if (matchingItem != null)
{
itemsToBeLast.Remove(matchingItem);
continue;
}
allItems.Add(itemsToBeLast[i]);// (attachDetachItem);
}
I've melihat banyak pilihan untuk ini, tapi untuk saya yang satu ini adalah yang terbaik.
ListItemCollection collection = new ListItemCollection();
foreach (ListItem item in ListBox1.Items)
{
if (item.Selected)
collection.Add(item);
}
Maka cukup loop melalui koleksi.
Diketahui bahwa ListItemCollection dapat berisi duplikat. Secara default tidak ada yang mencegah duplikat ditambahkan ke koleksi. Untuk menghindari duplikat anda dapat melakukan ini:
ListItemCollection collection = new ListItemCollection();
foreach (ListItem item in ListBox1.Items)
{
if (item.Selected && !collection.Contains(item))
collection.Add(item);
}
Oke jadi apa yang membantu saya adalah iterasi ke belakang. Aku mencoba untuk menghapus entri dari daftar tapi iterasi ke atas dan itu mengacaukan loop karena masuknya didn't ada lagi:
for (int x = myList.Count - 1; x > -1; x--)
{
myList.RemoveAt(x);
}
Berikut ini adalah skenario tertentu yang menjamin berupa pendekatan:
Kamus
dihitung sering.Kamus
dimodifikasi jarang.Dalam skenario ini membuat salinan dari Kamus
(atau Kamus.Nilai-nilai
) sebelum pencacahan dapat cukup mahal. Ide saya tentang pemecahan masalah ini adalah dengan menggunakan kembali salinan cache yang sama di beberapa mantri, dan menonton sebuah IEnumerator
asli Kamus
untuk pengecualian. Pencacah akan di-cache bersama dengan data yang disalin, dan diinterogasi sebelum memulai yang baru pencacahan. Dalam kasus pengecualian salinan cache akan dibuang, dan yang baru akan dibuat. Berikut adalah implementasi dari ide ini:
public class SafeEnumerable<T> : IEnumerable<T>, IDisposable
{
private IEnumerable<T> _source;
private IEnumerator<T> _enumerator;
private T[] _cached;
public SafeEnumerable(IEnumerable<T> source)
{
_source = source ?? throw new ArgumentNullException(nameof(source));
}
public IEnumerator<T> GetEnumerator()
{
if (_source == null) throw new ObjectDisposedException(this.GetType().Name);
if (_enumerator == null)
{
_enumerator = _source.GetEnumerator();
_cached = _source.ToArray();
}
else
{
var modified = false;
if (_source is ICollection collection) // C# 7 syntax
{
modified = _cached.Length != collection.Count;
}
if (!modified)
{
try
{
_enumerator.MoveNext();
}
catch (InvalidOperationException)
{
modified = true;
}
}
if (modified)
{
_enumerator.Dispose();
_enumerator = _source.GetEnumerator();
_cached = _source.ToArray();
}
}
foreach (var item in _cached)
{
yield return item;
}
}
public void Dispose()
{
_enumerator?.Dispose();
_enumerator = null;
_cached = null;
_source = null;
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
public static SafeEnumerable<T> AsSafeEnumerable<T>(this IEnumerable<T> source)
=> new SafeEnumerable<T>(source);
Contoh penggunaan:
private static IDictionary<Guid, Subscriber> _subscribers;
private static SafeEnumerable<Subscriber> _safeSubscribers;
//...(in the constructor)
_subscribers = new Dictionary<Guid, Subscriber>();
_safeSubscribers = _subscribers.Values.AsSafeEnumerable();
// ...(elsewere)
foreach (var subscriber in _safeSubscribers)
{
//...
}
Sayangnya ide ini tidak dapat digunakan saat ini dengan kelas Kamus
di .NET Core 3.0, karena kelas ini tidak melempar Koleksi dimodifikasi pengecualian ketika disebutkan dan metode Hapus
dan Hapus
yang dipanggil. Semua wadah lainnya saya diperiksa berperilaku secara konsisten. Aku memeriksa secara sistematis kelas-kelas ini:
Daftar<T>
, Koleksi<T>
, ObservableCollection<T>
, HashSet<T>
, SortedSet<T>
, Kamus<T,V>
dan Sorteddictionarystruktur<T,V>
. Hanya dua tersebut metode Kamus
di kelas .NET Core tidak membatalkan pencacahan.
Update: aku tetap masalah di atas dengan membandingkan juga panjang dari cache dan koleksi asli. Perbaikan ini mengasumsikan bahwa kamus ini akan diteruskan langsung sebagai argumen untuk SafeEnumerable
's konstruktor, dan identitasnya tidak akan tersembunyi dengan (misalnya) proyeksi seperti: kamus.Pilih(e => e).AsSafeEnumerable()
.
Jadi cara yang berbeda untuk memecahkan masalah ini akan menjadi bukan menghapus elemen-elemen yang membuat sebuah kamus baru dan hanya menambahkan unsur-unsur yang anda tidak ingin hapus kemudian ganti kamus asli dengan yang baru. Saya don't pikir ini terlalu banyak masalah efisiensi karena tidak meningkatkan jumlah kali anda iterate atas struktur.
Ada satu link di mana itu diuraikan dengan sangat baik & solusi juga diberikan. Cobalah jika anda punya solusi yang tepat silakan posting di sini agar yang lain dapat mengerti. Diberikan solusi ok lalu seperti posting lain dapat mencoba solusi ini.
untuk anda referensi link asli :- https://bensonxion.wordpress.com/2012/05/07/serializing-an-ienumerable-produces-collection-was-modified-enumeration-operation-may-not-execute/
Ketika kita gunakan .Net Serialisasi kelas untuk membuat sebuah objek di mana definisi berisi Enumerable jenis, yaitu koleksi, anda akan dengan mudah mendapatkan InvalidOperationException mengatakan "Koleksi dimodifikasi; pencacahan operasi mungkin tidak dapat mengeksekusi" di mana anda coding di bawah multi-thread skenario. Bagian bawah penyebabnya adalah serialisasi kelas akan iterate melalui pengumpulan melalui enumerator, seperti, masalah pergi untuk mencoba untuk iterate melalui pengumpulan sementara memodifikasi itu.
Solusi pertama, kita hanya dapat menggunakan kunci sebagai solusi sinkronisasi untuk memastikan bahwa operasi untuk Daftar objek hanya dapat dijalankan dari satu thread pada suatu waktu. Jelas, anda akan mendapatkan penalti kinerja yang jika anda ingin cerita bersambung koleksi dari objek tersebut, kemudian untuk masing-masing dari mereka, kunci akan diterapkan.
Nah, .Net 4.0 yang membuat berhadapan dengan multi-threading skenario berguna. untuk serialisasi Koleksi bidang masalah, saya menemukan kita hanya bisa mengambil manfaat dari ConcurrentQueue(Check MSDN)kelas, yang thread-safe dan FIFO koleksi dan membuat kode kunci-bebas.
Menggunakan kelas ini, dalam kesederhanaan, hal-hal yang anda perlu mengubah kode anda mengganti jenis Koleksi dengan itu, gunakan Enqueue untuk menambahkan elemen ke akhir ConcurrentQueue, menghapus orang-kode kunci. Atau, jika skenario yang sedang anda kerjakan tidak memerlukan pengumpulan hal-hal seperti Daftar, anda akan membutuhkan beberapa kode untuk beradaptasi ConcurrentQueue ke bidang anda.
BTW, ConcurrentQueue doesnât memiliki metode yang Jelas karena yang mendasari algoritma yang doesnât izin atom kliring dari koleksi. jadi, anda harus melakukannya sendiri, cara tercepat adalah untuk menciptakan kembali kosong baru ConcurrentQueue untuk penggantian.
Jawaban yang diterima adalah tidak tepat dan tidak benar dalam kasus terburuk . Jika ada perubahan yang dibuat selama Kedaftar()
, anda masih dapat berakhir dengan kesalahan. Selain lock
, yang kinerja dan benang pengaman yang perlu dipertimbangkan jika anda memiliki anggota publik, solusi yang tepat dapat menggunakan berubah jenis.
Secara umum, abadi jenis berarti bahwa anda dapat't mengubah negara itu setelah dibuat. Sehingga kode anda akan terlihat seperti:
public class SubscriptionServer : ISubscriptionServer
{
private static ImmutableDictionary<Guid, Subscriber> subscribers = ImmutableDictionary<Guid, Subscriber>.Empty;
public void SubscribeEvent(string id)
{
subscribers = subscribers.Add(Guid.NewGuid(), new Subscriber());
}
public void NotifyEvent()
{
foreach(var sub in subscribers.Values)
{
//.....This is always safe
}
}
//.........
}
Hal ini dapat sangat berguna jika anda memiliki anggota publik. Kelas-kelas lain selalu dapat foreach
pada berubah jenis tanpa khawatir tentang koleksi yang dimodifikasi.
Saya pribadi berlari melintasi baru-baru ini di asing kode di mana itu di dbcontext dengan Linq's .Menghapus(item). Aku punya satu neraka dari waktu mencari kesalahan debugging karena item(s) mendapat dihapus pertama kali iterasi, dan jika saya mencoba untuk melangkah kembali dan re-langkah di atas, koleksi itu kosong, karena saya masih dalam konteks yang sama!