« Wer von seinem Tag nicht 2 Drittel für sich selbst hat, ist ein Sklave. | Main| Verkaufsmethoden :-D »

Die Auflistung wurde geändert. Der Enumerationsvorgang kann möglicherweise nicht ausgeführt werden.

10
Kategorie   
So oder eine ähnliche Meldung hat jeder bestimmt schon mal bekommen.
Liegt meistens daran, dass man in einem Thread schreiben und in einem anderen versucht mit dem Enumerator drüber zu stampfen
Mit dem Stück-Code (bestimmt) nicht schön, kann man die Fehlermeldung provozieren ->
 class Program {
        private staticArrayList al = newArrayList();

        static void Main(string[] args) {
            Thread t1 = newThread(AddElements);
            Thread t2 = newThread(ReadElements);
            t1.Start();
            t2.Start();
        }

        static void AddElements() {
            Random r = newRandom();
            while (true) {
                al.Add(r.NextDouble());
                System.Console.WriteLine("... added " + al.Count);
                Thread.Sleep(100);
            }
        }

        static void ReadElements() {
            while (true) {
                IEnumerator e = al.GetEnumerator();
                System.Console.WriteLine("Start Enumerator " + al.Count);
                while (e.MoveNext()) {
                    object o = e.Current;
                }
                System.Console.WriteLine("End Enumerator " + al.Count );
            }
        }
    }

Welche Abhilfen gibt es.... klar man könnte synchronisieren bis Teufel kommt raus, d.h. z.b. keine neuen Elemente hinufügen, solange jemand drüber iteriert

Man kann das ganze auch durch ein CopyOnWrite-Pattern lösen, dass heißt vereinfacht man baut eine kleine Klasse, welche eine Referenz auf eine Array hat. In der zugehörigen Add-Methode wird ein Clone erzeugt, bei diesem das neue Element angehängt und nach Vollzug die Referenz ersetzt. Holt sich jemand den Enumerator auf eine Referenz, dann ist diese für die Zeit des "enumerieren" stabil, man sieht natürlich nur die Daten, welche in der ArrayList zum Zeitpunkt des Erstellen des Enumerators waren. Hier ein Implementierungsbeispiel:
publicclass CopyOnWriteArrayList {

        private ArrayList al = null;
        static object l = new object();

        public long Count {
            get {
                return al.Count;
            }
        }
        public CopyOnWriteArrayList() {
            al = new ArrayList();
        }

        public void Add( object o) {
            lock(l) {
                ArrayList snapshot = (ArrayList)al.Clone();
                snapshot.Add(o);
                al = snapshot;
            }
        }

        public IEnumerator GetEnumerator() {
            return al.GetEnumerator();
        }

    }
Und hier die Zeilen zum Testen ->
classProgram {
        private staticCopyOnWriteArrayList al = newCopyOnWriteArrayList();

        private staticvoid Main(string[] args) {
            Thread t1 = newThread(AddElements);
            Thread t2 = newThread(ReadElements);
            t1.Start();
            t2.Start();
        }

        private staticvoid AddElements() {
            Random r = newRandom();
            while (true) {
                al.Add(r.NextDouble());
                System.Console.WriteLine("... added " + al.Count);
                Thread.Sleep(100);
            }
        }

        private staticvoid ReadElements() {
            while (true) {
                IEnumerator e = al.GetEnumerator();
                System.Console.WriteLine("Start Enumerator " + al.Count);
                while (e.MoveNext()) {
                    object o = e.Current;
                }
                System.Console.WriteLine("End Enumerator " + al.Count);
            }
        }
    }

Gruß JJR


Kommentare

Gravatar Image1 - Hallo Jörg,

ein sehr interessanter Beitrag - ich erinnere mich noch dran wie wir dieses Problem zu Beginn unseres Projektes zu hauf hatten Emoticon

Ich habe deinen Code angesehen und mich gefragt wozu das "lock(l)" in der Add-Methode gut ist, da ein Lock doch nur Sinn macht wenn es mindestens noch an einer zweiten konkurrierenden Stelle aufgerufen wird, also hier im Auslesen durch den Iterator... So lockt immer nur der Add-Thread ein völlig unabhängiges statisches Objekt während er clont und die Instanz der ArrayList neu setzt - d.h. du würdest damit nur erreichen dass nicht zwei Threads gleichzeitig Add aufrufen, sonst eigentlich nichts. Bitte korrigier mich wenn ich falsch liege, aber wenn du das lock einfach weglässt geht das Beispiel auch noch - ich hab's gerade ausprobiert Emoticon

Jetzt kommt die spannende Frage: wieso geht es auch ohne das lock?? Ich habe gerade gegrübelt und kann es mir eigentlich nur so erklären, dass das komplette Neusetzen der Instanzvariablen "al=snapshot" aus irgendwelchen Gründen nicht die Exception auslöst während ein direktes Add auf die Ausgangsinstanz den Iterator hingegen offenbar ungültig macht. Hm, mysteriös... Emoticon

Viele Grüße,
Sebastian



Gravatar Image2 - Hallo Sebastian,

das lock beim Add habe ich gemacht, weil wenn ich es nicht gemacht hätte, dann hätten es manche ohne lock angewendet und dann geht es nicht. Also Liste hat 1,2 und ein Thread fügt 3 und ein andere 4 hinzu, dann habe ich am Ende entweder Liste 1,2,3 oder 1,2,4 beides irgendwie doof, deshalb geht das lock bis zum setzen der referenz Emoticon

Das Neusetzen der Instanzvariablen kann die Exception nicht auslösen. Im Speicher sieht man beim ersten Lesen des Iterators die Referenz auf al1, d.h. der Enumerator wird auf al1 arbeiten. Wenn nun ein Add gemacht wird, dann wird al2 erzeugt und auf die instanzvariable geschrieben. al1 wurde nicht verändert also keine Exception. Deshlab schrieb ich ja auch -> man sieht natürlich nur die Daten, welche in der ArrayList zum Zeitpunkt des Erstellen des Enumerators waren Emoticon
Was aber in den meisten Fällen ausreichend sein sollte, auch bei dem von Dir angesprochenen Projekt Emoticon

Gruß JJR

Gravatar Image3 - Hallo Jörg,

wenn mehr als ein Thread gleichzeitig Elemente in die Liste einfügt ist das lock natürlich sinnvoll - touché Emoticon

Das mit den sichtbaren Daten ist m.E. auch o.k., besser als eine Exception ist's ja allemal. Da kommen gleich Erinnerungen an die guten alten with-nolock-Zeiten wieder hoch Emoticon

Schönen Abend noch,
Sebastian

Gravatar Image4 - Hallo Sebastion,

vor allen Dingen, wenn die Ergebnisse leicht nachvollziehbar und es eine Exception-Count-Competition im Projekt gibt Emoticon

Gruß JJR

Mach einen Kommentar

:-D:-o:-p:-x:-(:-):-\:angry::cool::cry::emb::grin::huh::laugh::lips::rolleyes:;-)

Amazon


Impressum

Firmenname: Peanuts-Soft
Straße Nummer: Biinger Strasse 8
PLZ Ort: 55263 Wackernheim
Telefon: +491772134526
E-Mail: joerg.reck @ peanuts-soft.de
Disclaimer: Peanuts-Soft übernimmt keine Garantie dafür, dass die auf dieser Website bereitgestellten Informationen vollständig, richtig und stets aktuell sind. Dies gilt auch für alle Links, auf die verwiesen wird. Peanuts-Soft ist für die Inhalte, auf die per Link verwiesen wird, nicht verantwortlich. Peanuts-Soft haftet nicht für konkrete, mittelbare und unmittelbare Schäden oder Schäden, die durch fehlende Nutzungsmöglichkeiten, Datenverluste oder entgangene Gewinne – sei es aufgrund der Nichteinhaltung vertraglicher Verpflichtungen, durch Fahrlässigkeit oder eine andere unerlaubte Handlung – im Zusammenhang mit der Nutzung von Dokumenten oder Informationen bzw. der Erbringung von Dienstleistungen entstehen, die auf dieser Web Site zugänglich sind.
Datenschutz: Inhalt und Gestaltung der Internetseiten sind urheberrechtlich geschützt. Eine Vervielfältigung der Seiten oder deren Inhalte bedarf der vorherigen schriftlichen Zustimmung von Peanuts-Soft.


Locations of visitors to this page

Powered By

Domino BlogSphere
Version 3.0.2