Foreach durch ändernde List und Array

  • C#
  • .NET (FX) 4.5–4.8

Es gibt 6 Antworten in diesem Thema. Der letzte Beitrag () ist von EaranMaleasi.

    Foreach durch ändernde List und Array

    Hallo zusammen,

    ich habe mal eine Frage bezüglich das druchloopen von Listen und Arrays.

    Folgender Code:

    C#-Quellcode

    1. private static int[] iArr = new int[0];
    2. static void Main(string[] args)
    3. {
    4. Thread t1 = new Thread(FülleArray);
    5. t1.Start();
    6. Thread t2 = new Thread(LeseArray);
    7. t2.Start();
    8. Console.ReadKey();
    9. }
    10. private static void FülleArray()
    11. {
    12. while (true)
    13. {
    14. iArr = new int[5000];
    15. for (int i = 0; i < 5000; i++)
    16. {
    17. iArr[i] = i;
    18. Console.WriteLine("Thread 1:" + i);
    19. }
    20. }
    21. }
    22. private static void LeseArray()
    23. {
    24. while (true)
    25. {
    26. foreach (int i in iArr)
    27. {
    28. Console.WriteLine("Thread 2:" + i);
    29. }
    30. }
    31. }


    das Resultat sieht nun so aus, dass beide Threads scheinbar Ihr eigenes Array generieren und diese werden ohne Fehler vollständig durchgeloopt.

    Änder ich nun das Array zu einer List<int>

    C#-Quellcode

    1. private static List<int> iArr = new List<int>();
    2. static void Main(string[] args)
    3. {
    4. Thread t1 = new Thread(FülleArray);
    5. t1.Start();
    6. Thread t2 = new Thread(LeseArray);
    7. t2.Start();
    8. Console.ReadKey();
    9. }
    10. private static void FülleArray()
    11. {
    12. while (true)
    13. {
    14. iArr.Clear();
    15. for (int i = 0; i < 5000; i++)
    16. {
    17. iArr.Add(i);
    18. Console.WriteLine("Thread 1:" + i);
    19. }
    20. }
    21. }
    22. private static void LeseArray()
    23. {
    24. while (true)
    25. {
    26. foreach (int i in iArr)
    27. Console.WriteLine("Thread 2:" + i);
    28. }
    29. }


    Dann kommt es spätestens beim Auslesen der Liste zu einer Exception, da die Enumeration sich ja geändert hat.

    Nun stellt sich mit die Frage, wieso das ganze bei einem normalen Array funktioniert.

    Änder ich nun den Lese-Thread wie folgt ab:

    C#-Quellcode

    1. private static void LeseArray()
    2. {
    3. while (true)
    4. {
    5. foreach (int i in iArr.ToArray())
    6. {
    7. Console.WriteLine("Thread 2:" + i);
    8. }
    9. }
    10. }


    kommt ebenfalls das gewünschte Ergebnis.

    Kann ich also mit einem Array Änderungen umgehen? Sodass ich gleichzeitig das Array durchloopen kann und ein andere Thread setzt hier andere Daten?

    Was genau geht hier vorsich?


    LG Marvin
    Was soll dieses

    MarvinKleinMusic schrieb:

    C#-Quellcode

    1. while (true)
    :?:
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Das ist relativ einfach.

    Dein Array und deine Liste werden ja auch auf unterschiedlichen Wegen erstellt.
    Dein Array wird zunächst auf 5000 Elemente Festgelegt, und dann werden diese 5000 elemente nach und nach mit Werten versehen.
    Deine Liste wird mit, sagen wir einfach mal, 4 Startelementen erstellt, und dann werden immer mehr hinzugefügt. Zwischendurch muss die Liste sich erweitern, weil du ja ingesamt 5000 Elemente in die Liste stopfst. Natürlich ändert sich bei der Liste dann die Auflistung an sich, während bei einem Array nur der Wert innerhalb der Auflistung ändert.

    Oben drauf hat jede Liste eine interne "Version" die mit jedem Add() hochgezählt wird. Wenn nun ein Enumerator bemerkt, dass diese Version nicht mehr der gleicht, die er sich gemerkt hat, wirft er die Exception.

    Wenn du die beiden wirklich vergleichen möchtest, Initialisiere die Liste mit dem Konstruktor der einen int für die InitialCapacity entgegen nimmt, und befülle die Liste exakt wie du es mit dem Array machst, über den index, anstatt dem Add(). Das sollte dann dasselbe Ergebnis wie beim Array liefern.

    RodFromGermany schrieb:

    Was soll dieses

    Das ist dafür da, damit die Threads endlos durchlaufen. Ist halt nur ein sporadischer Test.

    EaranMaleasi schrieb:

    Das ist relativ einfach.


    Dann erschließt sich mir aber immer noch nicht, wieso ich dem dem Array hier ein ganz neues Array anhängen kann, während er im anderen Thread ja noch das alte durchläuft.

    C#-Quellcode

    1. iArr = new int[5000];


    Was wäre denn, wenn es standardmäßig 5000 groß ist, ich dieses nun in dem einen Thread durchloope und im anderen setze ich es auf

    C#-Quellcode

    1. iArr = new int[4000];


    LG

    *Vollzitate entfernt* ~NoFear23m

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Nofear23m“ ()

    Vermutlich weil foreach einmal GetEnumerator() aufruft, und der erhaltene Enumerator eine Referenz auf das Objekt hat, von dem GetEnumerator() aufgerufen wurde. Dadurch läuft die foreach Schleife durch das alte Objekt durch, egal was du in die Variable haust. Setz sie doch mal testweise auf null. Wenn meine Theorie stimmt, dürfte das die lesende foreach Schleife nicht stören.

    EaranMaleasi schrieb:

    Setz sie doch mal testweise auf null. Wenn meine Theorie stimmt, dürfte das die lesende foreach Schleife nicht stören.


    C#-Quellcode

    1. private static void FülleArray()
    2. {
    3. while (true)
    4. {
    5. Random rdm = new Random();
    6. int size = rdm.Next(5000);
    7. //iArr = new int[size];
    8. iArr = null;
    9. if (iArr != null)
    10. {
    11. for (int i = 0; i < size; i++)
    12. {
    13. iArr[i] = i;
    14. Console.WriteLine("Thread 1:" + i);
    15. }
    16. }
    17. }
    18. }
    19. private static void LeseArray()
    20. {
    21. while (true)
    22. {
    23. foreach (int i in iArr)
    24. {
    25. Console.WriteLine("Thread 2:" + i);
    26. }
    27. }
    28. }


    Führt zu:
    System.NullReferenceException: "Der Objektverweis wurde nicht auf eine Objektinstanz festgelegt."

    "<Temporäre lokale Variable vom Typ "int[]">" war "null".


    LG
    Ich hab einen kleinen Denkfehler... Die Liste unterscheidet natürlich zwischen ihrer momentanen Capacity, und ihrer momentanen Size. Werte via Index abrufen und setzen, geht nur bis zur Size, nicht bis zur Capacity.
    Demnach muss die Liste zunächst mit einem Array Initalisiert werden, bevor man in einem Separaten Thread Werte einfügen kann:

    C#-Quellcode

    1. int[] arrInt = new int[5000];
    2. List<int> lstInt = new List<int>(arrInt);
    3. Task tFill = new Task(() =>
    4. {
    5. for (int i = 0; i < 5000; i++)
    6. {
    7. lstInt[i] = i;
    8. }
    9. });
    10. Task tRead = new Task(async () =>
    11. {
    12. foreach (int item in lstInt)
    13. {
    14. Console.WriteLine(item);
    15. await Task.Delay(10);
    16. }
    17. });
    18. tFill.Start();
    19. tRead.Start();
    20. Task.WaitAll(tFill);
    21. lstInt = null;