C# -Die Auflistung wurde geändert – Der Enumerationsvorgang kann möglicherweise nicht ausgeführt werden

Eine Meldung ähnlich wie die Überschrift kann man ganz leicht erhalten, wenn man mit Auflistungen in einer Anwendung arbeitet, welche mehrere Threads verwendet und ein Thread lesend und ein anderer schreiben auf die Auflistung zugreift.

Nun ist es so, dass es zu diesem Thema eine ganze Reihe, von mehr oder weniger, guten Ansätzen im Internet zu finden ist (Natürlich überwiegend gute 🙂 ). Wobei eine der entscheidenden Aussagen ist, dass man diese Fehlermeldungen, dann erhält wenn man mit einer Enumeration (foreach Schleife) über die Auflistung geht und dabei neue Elemente der Auflistung hinzufügen oder Elemente löschen möchte.

Also zum Beispiel wie in der folgenden Klasse:

public class AppointmentList : List<Appointment>
{
	public void DeleteAppointmentsForDate(DateTime dt)
	{
		foreach (Appointment appointment in this)
		{
			if (appointment.StartDate.Date == dt.Date)
			{
				Remove(appointment);
			}
		}
	}
}

Eigentlich soll, die Methode der Klasse alle Appointments eines bestimmten Datums aus der Auflistung löschen.
Das mach sie auch, und zwar dann, wenn alles in einem Thread abläuft.

Aber nur dann.

Nun wird bei den meisten Lösungen im Internet vorgeschlagen anstelle der Iteration mit foreach einfach eine for Schleife zu verwenden.

Das würde dann so aussehen
(Ich möchte aber gleich hier sagen, dass diese Lösung für das Löschen auch nicht eingesetzt werden kann, da diese egal ob mit oder ohne mehreren Threads nicht richtig arbeitet).

public class AppointmentList : List<Appointment>
{
		for (int i = 0; i < this.Count; i++)
		{
			if (this[i].StartDate.Date == dt.Date)
			{
				this.RemoveAt(i);
			}
		}
	}
}

Und warum geht das nicht ?

Nehmen wir der Einfachheit halber mal an die Auflistung würde 4 Einträge enthalten und alle 4 würden das gleiche Startdatum haben, was bedeuten würde, dass alle 4 Listen Elemente gelöscht werden müssten.

Runde 1:
i == 0 und this.count== 3
Element an Index 0 wird gelöscht
Element mit Index 1 wird nun an Index Position 0 geführt
Element mit Index 2 wird nun an Index Position 1 geführt
Element mit Index 3 wird nun an Index Position 2 geführt
this.count ist nun nur noch 2

Runde 2:
i == 1 und this.count == 2
….

Eigentlich brauche ich gar nicht mehr weiter erläutern, jetzt ist klar, dass das löschen mit der for schleife mit erhöhendem Index (i++) nicht funktionieren kann, wenigstens nicht wenn es um das Löschen von List Elementen geht.

Aber natürlich gibt es auch dafür eine einfache Lösung, und diese sieht so aus:

public class AppointmentList : List<Appointment>
{
	public void DeleteAppointmentsForDate(DateTime dt)
	{
            for (int i = this.Count - 1; i >= 0; i--)
            {
                if (this[i].StartDate.Date == dt.Date)
                {
                    this.RemoveAt(i);
                }
            }
		}
	}
}

Genau, wir verwenden einfach eine for schleife mit einem herabzählendem Index (i–).
Dies funktioniert auch mit mehreren Threads.

8 Gedanken zu „C# -Die Auflistung wurde geändert – Der Enumerationsvorgang kann möglicherweise nicht ausgeführt werden“

  1. Ein Ändern der Liste während einer Iteration ist verboten – egal ob durch andere Threads oder der Eigene, es kommt immer zu einer Exception.

    Fast alle Auflistungen sind NICHT multithreadsicher. Bei List sind ca. 3 Zähler am Werk. Ohne lock ist die wahrscheinlich groß, dass diese Zähler nach ein paar Einfüge- und Lösch-Vorgänge aus dem tritt kommen und nurnoch Müll zurück bleibt

    1. Hallo Michael,
      du hast sicherlich Rechts recht was das ändern (hinzufügen und entfernen von Elementen) in einer Iteration angeht.
      Das Thema lock ist hat wohl weniger mit den Zählern einer Liste zu tun, als damit bei gleichzeitigem schreibenden Zugriff aus mehreren Threads auf eine Liste die Zugriffe der einzelnen Threads zu koordinieren und sicherzustellen, dass nur ein Thread zu einer Zeit Änderungen an der Liste vornimmt.

      Beste Grüße
      HP

  2. Bin heute wieder auf diesen Fehler gestossen, und hätte beinahe diese for-Schleife genommen. Bis mir wieder einfiel, das ging doch woanders auch. Dann habe ich gesehen, ah ein break, nach dem Fund. Durch den break, wird die Liste dann auch nicht mehr betrachtet anscheinend.
    Das hilft natürlich nur, wenn das zu entfernene Item eindeutig ist.

    1. Hallo Ron,
      wenn es „nur“ um das löschen von Elemente der Auflistung geht hast du natürlich recht, doch ändert es nichts an dem Problem, wenn man versucht in einer foreach Schleife Elemente einer Auflistung zu löschen, und dem daraus resultierenden Laufzeitfehler der erzeugt wird.

      Lg
      HP

  3. Guten Tag Hans-Peter,
    der Beitrag ist zwar schon etwas älter aber ich denke gerade für Einsteiger ist dieses Problem zunächst mal eine Hürde.
    Ich bin gerade zum zweiten mal in solch einen Fehler gelaufen und kannte bisher auch nur den Trick mit der rückwärts laufenden Schleife.
    Ich glaube jedoch nicht, dass damit das Problem der Thread-unsicherheit gelöst ist.
    Denn auch bei der Rückwärts laufenden Schleife kann ein Element der Liste durch andere Threads entfernt werden.
    Zumindest habe ich gerade trotzdem das Problem. Zugegeben, es handelt sich um eine Menge Threads und recht große Listen. Aber ich laufe trotzdem in diesen Fehler

    (Möglicher Fehler: durchlaufe ich die Liste Rückwärts bei z.B. 100 Elementen und befinden mich Momentan bei Element 98. Nun werden durch andere Threads aber z.B. 6 Elemente entfernt, dann haut es die Schleife in jedem Fall aus den Socken. Ich bin mir nicht sicher, ob das DIESEN speziellen Fehler verursacht.)

    Anmerkung zum Beispiel:
    müsste in zu Beginn von Runde 1 der Count nicht 4 sein bei 4 Listenelementen?
    Der Count ist i.d.R. ja nicht 0-basiert.

    Grüße,
    Jan

  4. Moin alle!

    Ich bin auf diesen Blogbeitrag auf der Suche nach Threadsicherheit für ein Dictionary gestoßen. Der hier angegebene Trick funktioniert bei Dictionaries in jedem Fall nicht.

    (Ich weiß nicht, ob die Situation vor zwei Jahren anders war, aber…) seit .NET Framework 4 gibt es aber eine einfachere Möglichkeit, Threadsicherheit bei Listen und Dictionaries zu erzeugen. Falls das jemand liest, der genauso wie cih auf der Suche war. Schau die mal die Klasse ConcurrentDictionary (https://msdn.microsoft.com/de-de/library/dd287191(v=vs.110).aspx) an. Listen bekommt man am leichtesten Threadsicher, in dem man möglichst nur auf Collection<> setzt und diese wiederrum mit lock absichert.
     

    Gruß Ronny

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.