none
Mulithreads Ergebnis zur Liste hinzufügen? RRS feed

  • Frage

  • Hallo,

    ich bin mir sicher, dass die Frage, schon irgendwo beantwortet wurde, aber ich weiß nicht so recht, wonach ich suchen soll.

    Ich habe mehrere Threads, deren Ausführungszeit zwischen 1 und 60 Sekunden beträgt. Nach der Bearbeitung eines jeden einzelnen Threads, soll dessen Ergebnis in eine Liste hinzugefügt werden. Mir fallen mehrere Möglichkeiten ein, wobei ich die letztere bevorzuge, jedoch nicht genau weiß, wie ich das anstelle. Bitte korrigiert mich, falls ich falsch liege.

    Ich könnte einen Typ aus dem System.Collections.Concurrent-Namespace verwenden, bei der jeder Thread nach der eigenen Abarbeitung sein Ergebnis in die Liste hinzufügt.

    Oder ich mache Folgendes (funzt das?)

               

     var objects = new List<object>();
                Parallel.For(0, 1000,()=>
                    {
                            lock (objects)
                        {
                                objects.Add(new object());
                        }
                    });

    Das beste erscheint mir eigentlich, die Threads alle arbeiten zu lassen und dann in einem einzelnen Thread mit den gesammelten Infos der ganzen Threads die Objekte hinzuzufügen. Am besten der Thread, der durch das ButtonClickereignis im WPF diese längere Aktion in Gang gesetzt hat. Nur weiß ich nicht so recht wie ich das anstelle.

    Ich danke euch schon einmal für eure Hilfe.



    Mittwoch, 29. Mai 2013 13:19

Antworten

  • Hallo BlackMatrix,

    dein Code wird leider nicht tun, die übergebene Action benötigt noch ein Argument (i):

    var objects = new List<object>();
    Parallel.For(0, 1000, i => {
       lock (objects) {
          objects.Add(new object());
       }
    });

    Das durch lock verursachte Blockieren ist - wie Elmar das bereits ausführte - nicht gerade optimal bei dieser hohen Anzahl an parallel ausgeführten Aktionen.

    Da es keine konkurrente Version von List<T> gibt, würde ich die Verwendung einer BlockingCollection mit einer zugrundeliegenden ConcurrentQueue empfehlen:

    Random rand = new Random();
    
    ConcurrentQueue<object> queue = new ConcurrentQueue<object>();
    BlockingCollection<object> blockingCollection = new BlockingCollection<object>(queue);
    
    Task[] tasks = new Task[10];
    
    for (int i = 0; i < tasks.Length; i++)
    {
        var currentTask = i;
        var delay = rand.Next(100, 6000); 
        tasks[currentTask] = Task.Factory.StartNew(() => 
        { 
            Thread.Sleep(delay); // Arbeit simulieren
            Console.WriteLine("Starting task {0}", currentTask); 
            blockingCollection.Add(new object()); 
        });
    }
    
    Task.WaitAll(tasks);
    Console.WriteLine("\nDone.");
    
    Console.WriteLine("\nPress any key.");
    Console.ReadKey(true);
    

    Die Vorteile der BlockingCollection werden vor allem beim gleichzeitigen Produzieren und Konsumieren sichtbar. Konkurrente Auflistungen sind u.U. langsamer als lock-Konstrukte beim Schreiben, allerdings sind sie viel schneller beim Lesen.

    s.a. Concurrent Collections

    Gruß
    Marcel

    Mittwoch, 29. Mai 2013 14:52
    Moderator
  • Hallo,

    funktionieren tut es, aber u. U. schlecht; denn durch die Sperre kann immer nur ein Thread auf die Auflistung zugreifen. Was wiederum zu einem "Stau" führen kann.

    Da Deine Beschreibung anders als der Code aussieht:
    Fügt jeder Thread nur am Ende ein einzelnes Element hinzu, so würde es eine List(T) mit lock tun. Fügen die Threads während ihrer Laufzeit (viele) Elemente hinzu, ist die BlockingCollection(T) eine bessere Lösung -  dann als ConcurrentBag, siehe: Verwendung einer threadsicheren Auflistung

    Gruß Elmar

    Mittwoch, 29. Mai 2013 14:18
    Beantworter

Alle Antworten

  • Hallo,

    funktionieren tut es, aber u. U. schlecht; denn durch die Sperre kann immer nur ein Thread auf die Auflistung zugreifen. Was wiederum zu einem "Stau" führen kann.

    Da Deine Beschreibung anders als der Code aussieht:
    Fügt jeder Thread nur am Ende ein einzelnes Element hinzu, so würde es eine List(T) mit lock tun. Fügen die Threads während ihrer Laufzeit (viele) Elemente hinzu, ist die BlockingCollection(T) eine bessere Lösung -  dann als ConcurrentBag, siehe: Verwendung einer threadsicheren Auflistung

    Gruß Elmar

    Mittwoch, 29. Mai 2013 14:18
    Beantworter
  • Hallo BlackMatrix,

    dein Code wird leider nicht tun, die übergebene Action benötigt noch ein Argument (i):

    var objects = new List<object>();
    Parallel.For(0, 1000, i => {
       lock (objects) {
          objects.Add(new object());
       }
    });

    Das durch lock verursachte Blockieren ist - wie Elmar das bereits ausführte - nicht gerade optimal bei dieser hohen Anzahl an parallel ausgeführten Aktionen.

    Da es keine konkurrente Version von List<T> gibt, würde ich die Verwendung einer BlockingCollection mit einer zugrundeliegenden ConcurrentQueue empfehlen:

    Random rand = new Random();
    
    ConcurrentQueue<object> queue = new ConcurrentQueue<object>();
    BlockingCollection<object> blockingCollection = new BlockingCollection<object>(queue);
    
    Task[] tasks = new Task[10];
    
    for (int i = 0; i < tasks.Length; i++)
    {
        var currentTask = i;
        var delay = rand.Next(100, 6000); 
        tasks[currentTask] = Task.Factory.StartNew(() => 
        { 
            Thread.Sleep(delay); // Arbeit simulieren
            Console.WriteLine("Starting task {0}", currentTask); 
            blockingCollection.Add(new object()); 
        });
    }
    
    Task.WaitAll(tasks);
    Console.WriteLine("\nDone.");
    
    Console.WriteLine("\nPress any key.");
    Console.ReadKey(true);
    

    Die Vorteile der BlockingCollection werden vor allem beim gleichzeitigen Produzieren und Konsumieren sichtbar. Konkurrente Auflistungen sind u.U. langsamer als lock-Konstrukte beim Schreiben, allerdings sind sie viel schneller beim Lesen.

    s.a. Concurrent Collections

    Gruß
    Marcel

    Mittwoch, 29. Mai 2013 14:52
    Moderator
  • Ich danke euch für eure Antworten, vor allem das man die BlockingCollection für die Basis eines anderes Typs verwenden kann, wusste ich noch nicht.

    Den Actiondelegaten hatte ich vergessen, weil ich dies von der Parallel.ForEach-Methode abgeschaut hatte. Es sind zwar 1000 Tasks, die theoretisch gleichzeitig abgearbeitet werden könnten, aber in meinem realen Szenario lasse ich immer nur 20 parallel (über ParallelOptions) laufen.

    Was ich mich ja gefragt hatte, muss man denn, obwohl die Tasks ja zu 99% nur arbeiten und nichts mit der BlockingCollection tun, trotzdem solch einen ConcurrentTyp verwenden? Denn eigentlich könnte man ja diese Ergebnisse einfach nur sammeln und dann in einem einzelnen Thread der Collection hinzufügen. Oder entspricht das dem Verhalten eines solchen Typs?

    Donnerstag, 30. Mai 2013 15:44