Laden...

Ergebnis einer Datenbankabfrage an Methode übergeben

Erstellt von micha0827 vor 6 Jahren Letzter Beitrag vor 6 Jahren 20.109 Views
M
micha0827 Themenstarter:in
85 Beiträge seit 2015
vor 6 Jahren
Ergebnis einer Datenbankabfrage an Methode übergeben

verwendetes Datenbanksystem: MS SQL 2016

Hallo zusammen,

ich steh ein bischen auf dem Schlauch. Wie kann ich das Ergebnis der folgenden Abfrage an eine Methode übergeben.


var auktionen = (from a in db.bayEbayTracking
                                 join b in db.bayEbayCountry on a.globalID equals b.id
                                 where a.aktiv != false && a.abgelaufen == null
                                 orderby a.id
                                 select new { bayEbayTracking = a, bayEbayCountry = b }).ToList();

                List<Task<IList<string[,]>>> taskList = new List<Task<IList<string[,]>>>();

                for (int i = 0; i < auktionen.Count; i += 500)
                {
                    var block = auktionen
                        .Select(art => new { art.bayEbayTracking.artikelnummer, art.bayEbayTracking.keywords, art.bayEbayCountry.GlobalID })
                        .Skip(i)
                        .Take(500)
                        .ToList();

                    taskList.Add(this.StartCall(block));
                }

in block steht drin:

[0]{ artikelnummer = "1111111111", keywords = "iPhone", GlobalID = "EBAY-DE" }
[1] .....

Wie muss ich die Parameter in der aufgerufenen Methode definieren ? Mit StartCall(List<string[,,]> block) kommt ein Fehler.

Alternativ könnte ich natürlich eine neue Liste definieren und per Schleife aus der DB Abfrage füllen, aber ist das nicht ein Umweg ?

Danke
Michael

16.834 Beiträge seit 2008
vor 6 Jahren

Anonyme Typen können nicht ausserhalb der jeweiligen Methode verwendet werden.
Musst Du in eine Klasse mit entsprechender Instanz überführen und diese dann zurück geben.

M
micha0827 Themenstarter:in
85 Beiträge seit 2015
vor 6 Jahren

ich habe jetzt das gemacht:


class ArtikelListe
    {
        public List<Artikel> Artikel { get; set; }
    }

    public class Artikel
    {
        public string artikelnummer { get; set; }
        public string keywords { get; set; }
        public string GlobalID { get; set; }
    }

gibt es denn einen eleganten Weg die Variable block in die Klasse Artikelliste zu bringen ?

Michael

16.834 Beiträge seit 2008
vor 6 Jahren

Elegant ist Ansichtssache. Unelegant ist allein schon nicht das Einhalten der [Artikel] C#: Richtlinien für die Namensvergabe.
Was genau mit Elegant meinst Du denn?

M
micha0827 Themenstarter:in
85 Beiträge seit 2015
vor 6 Jahren

Danke für den Namenshinweis.

Naja, unelegant wäre die Klasse per Schleifendurchlauf durch die block Variable zu "füllen". Elegant wäre sowas wie

Artikelliste artikelliste = new Artikelliste()
artikelliste = block;

Michael

16.834 Beiträge seit 2008
vor 6 Jahren

Ich bin mir nicht sicher, was Du hier genau vor hast; vor allem weil da auch noch irgendwas mit Tasks steht.
Und da dann auch der Hinweis, dass Datenbankverbindungen in .NET (sofern sie über ADO.NET laufen) nicht Thread-Safe sind.

Man sollte evtl. mal nen weiteren Überblick haben, was Du da eigentlich machen willst, um Dir irgendein "Elegant"-Feedback geben zu können.
Allgemein aber haben Tasks im DAL eher nichts zu suchen.

M
micha0827 Themenstarter:in
85 Beiträge seit 2015
vor 6 Jahren

ich würde gern je 500 Datensätze einer DB Abfrage in einem Task verarbeiten. Dazu wollte ich die Schleife durchlaufen und eine Liste mit den 500 Datensätzen an den Thread geben.

Michael

16.834 Beiträge seit 2008
vor 6 Jahren

Das ist aber Aufgabe der Business Logik und nicht des Datenbanklayers.
Dazu müsste Deine Business Logik eben aufgrund der Tatsache, dass ADO.NET Datenbank-Provider nicht Threadsicher sind, auch für jedes Laden eines 500 Chunks jeweils eine eigene Datenbankverbindung öffnen, sofern das parallel passieren soll (durch Tasks).
Dein Datenbanklayer erhält i.d.R. die jeweilige Connection von der Business Logik und darf diese nicht selbst erstellen.

Anders hier ist zB. eine DocumentDB / CosmosDB, dessen Verbindungen Thread-Safe sind.

5.658 Beiträge seit 2006
vor 6 Jahren

Hi micha0827,

abgesehen davon, was Abt bereits geschrieben hat (und ich an anderer Stelle: [Artikel] Drei-Schichten-Architektur), gibt es bereits eine fertige Lösung für die parallele Verarbeitung von Collections: Introduction to PLINQ.

Weeks of programming can save you hours of planning

M
micha0827 Themenstarter:in
85 Beiträge seit 2015
vor 6 Jahren

@Abt

also du meinst statt die Datenbank zu öffnen, 20.000 Datensätze in 500er Blöcke aufzuteilen und die in 40 Tasks weiterzuverarbeiten wäre es besser die Datenbank zu öffnen, die Connection an die 40 Tasks weiterzugeben und in jedem Task 500 Datensätze abzufragen ?

Michael

16.834 Beiträge seit 2008
vor 6 Jahren

Aber auch PLINQ darf nicht gegen einen Threadunsicheren DB Context laufen!

Naja, Du kannst schon Chunks laden, aber mit einem Context, der nicht Thread-Safe ist, halt nicht parallel.

Wenn Du eh alle sofort laden willst, was Du hier machen willst, macht es eh nicht sooo viel sinn das parallel laufen zu lassen, weil jeder dieser Select-Query natürlich neu von der DB verarbeitet wird und im schlimmsten Fall mehrere Full-Table-Scans ausgeführt werden.

Ob das also wirklich zeitsparend ist oder gar am Ende mehr Zeit kostet als ein "großer" Query.... bezweifle bei einem ordentlichen Schema mal.
Das mal vorher überhaupt getestet, bevor eine Zeile Code geschrieben wurde? 😉

Besser:
Wenn man eh den SQL Server nimmt und dann sich für Chunks entscheidet, dann kann man direkt das OFFSET FETCH Feature nutzen, denn damit läuft es dann auch wirklich performanter. OFFSET FETCH ist eben für Pagination gemacht.
Ich bin eh kein Fan von Entity Framework oder Linq2sql (oder was Du da auch nutzt), sondern von Dapper und ordentlichen SQL Befehlen.
OFFSET FETCH kann kein Linq-Provider, sondern geht nur über einen eigenen SQL Befehl.

M
micha0827 Themenstarter:in
85 Beiträge seit 2015
vor 6 Jahren

ich denke jetzt sind wir etwas abgewichen, Frage ist immer noch wie man einen anonymen Typ in eine Klasse bringt.

Michael

5.658 Beiträge seit 2006
vor 6 Jahren

Meinst du das:

block = auktionen
    .Select(art => new Artikel()
    { 
        artikelnummer = art.bayEbayTracking.artikelnummer, 
        keywords = art.bayEbayTracking.keywords, 
        GlobalID = art.bayEbayCountry.GlobalID 
    })

Weeks of programming can save you hours of planning

16.834 Beiträge seit 2008
vor 6 Jahren

Kleine Korrektur:

ich sehe gerade, dass Du im eigentlichen Query ja ToList verwendest.
Dadurch rufst Du alle Daten auf ein mal ab.

Das, was Du da in Tasks aufteilst, sollte - sofern man das hier sieht - keine Aktion auf den Db Context erzeugen, sondern sich nur auf Daten beziehen, die eh im RAM liegen.
Damit kann man mit einem PLINQ Query alles sehr schnell und mit wenig Code parallelisieren.
Hat den Nachteil: bei vielen Daten gibt es eine OutOfMemoryException.

Du kannst an dieser Stelle überhaupt nicht mit anonymen Typen arbeiten, wenn diese ausserhalb der Methode verwendet werden müssen.
Das geht in .NET nicht. Da musst Du mit Klassen arbeiten und die anonymen Typen bzw. dessen Werte in Klassen übertragen.
zB eben

 .Select(x=> new Artikel()
    {
        artikelnummer = x.bayEbayTracking.artikelnummer,
        keywords = x.bayEbayTracking.keywords,
        GlobalID = x.bayEbayCountry.GlobalID
    })
M
micha0827 Themenstarter:in
85 Beiträge seit 2015
vor 6 Jahren

Ein Stück bin ich momentan weiter, so sieht es momentan aus:


public async Task<string> ArtikelAufteilen()
        {
            using (baygraphEntities db = new baygraphEntities())
            {
                var auktionen = (from a in db.bayEbayTracking
                                 join b in db.bayEbayCountry on a.globalID equals b.id
                                 where a.aktiv != false && a.abgelaufen == null
                                 orderby a.id
                                 select new { bayEbayTracking = a, bayEbayCountry = b }).ToList();

                List<Task<string>> taskList = new List<Task<string>>();
                
                for (int i = 0; i < auktionen.Count; i += 1000)
                {
                    var block = auktionen
                        .Select(art => new Artikel()
                        {
                            id = art.bayEbayTracking.id,
                            artikelnummer = art.bayEbayTracking.artikelnummer,
                            keywords = art.bayEbayTracking.keywords,
                            GlobalID = art.bayEbayCountry.GlobalID
                        })
                        .Skip(i)
                        .Take(1000)
                        .ToList();

                    var StartCallTask = StartCall(block);

                    taskList.Add(StartCallTask);

                    if (taskList.Count >= 10)
                    {
                        var completedTask = await Task.WhenAny(taskList).ConfigureAwait(false);
                        taskList.Remove(completedTask);
                    }
                    await Task.WhenAll(taskList).ConfigureAwait(false);
                }

                return "Ergebnis";
            }
        }

        private async Task<string> StartCall(List<Artikel> block)
        {
            return await Task.Run(() =>
            {
                Console.WriteLine($"Task gestartet: {Thread.CurrentThread.ManagedThreadId}");
                Thread.Sleep(500);
                return "";
            });

        }

Die Methode "StartCall" wird zwar mit unterschiedlichen Task gestartet, die aber nacheinander ausgeführt werden. Ich komm einfach nicht dahinter wo hier der Fehler liegt ?

Michael

J
641 Beiträge seit 2007
vor 6 Jahren

Man könnte schon mit anonymen Typen Arbeiten, wenn man die neuen ValueTupels nutzt.

cSharp Projekte : https://github.com/jogibear9988

16.834 Beiträge seit 2008
vor 6 Jahren

Value Tuples sind keine anonyme Typen sondern ganz einfach: Tuples 😉

@micha: das ist grundlegend etwas suboptimal; schon allein, dass Deine Methode selbst einen Datenbankkontext eröffnet ist prinzipiell nicht korrekt.
Aber gehts Dir hier um korrekter oder gehts Dir um "hauptsache geht" ?

M
micha0827 Themenstarter:in
85 Beiträge seit 2015
vor 6 Jahren

@Abt

natürlich ist korrekt immer die optimale Lösung. Allerdings bin ich Alleinkämpfer und brauche natürlich auch zeitnah eine Lösung.

Grundproblem ist, dass der Programmteil "StartCall()" sich aktuell in einer Konsolenanwendung befindet die ich 20x ausführe und die Skip & Take Optionen per Kommandozeile übergebe. Dies funktioniert, allerdings nicht unabhängig von mir. Letzten Sonntag gab es ein MS Update auf dem Server und das Programm lief nicht bis ich es Montag Morgen bemerkt habe. Das ist nicht optimal. Ich möchte gern die 20 Aufrufe in einer Anwendung bündeln (oder mehr, je nachdem wie die DB wächst) und die ganze Anwendung als WebJob in Azure laufen lassen.

Hatte ich mir leichter vorgestellt 😃

Michael

D
985 Beiträge seit 2014
vor 6 Jahren

Die Methode "StartCall" wird zwar mit unterschiedlichen Task gestartet, die aber nacheinander ausgeführt werden.

Deine Wahrnehmung ist da falsch, was wohl an dem fehlenden Wissen um die Arbeitsweise des ThreadPools liegt.

Dazu dein Beispiel etwas abgewandelt:


using System.Threading;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    class Program
    {
        static async Task Main(string[] args)
        {
            var source = Enumerable.Range(1, 20000).Select(e => e.ToString());
            Console.WriteLine("Start");
            string result;
            Console.WriteLine("1. Lauf");
            result = await ArtikelAufteilen(source);
            Console.WriteLine("2. Lauf");
            result = await ArtikelAufteilen(source);
            Console.WriteLine("Ende");
        }

        public static async Task<string> ArtikelAufteilen(IEnumerable<string> source)
        {
            var auktionen = source.ToList();

            var taskList = new List<Task<string>>();

            for (int i = 0; i < auktionen.Count; i += 1000)
            {
                var block = auktionen
                    .Skip(i)
                    .Take(1000)
                    .ToList();

                Console.WriteLine($"{DateTime.Now.ToString("hh:mm:ss.ffffff")} - StartCall");
                var startCallTask = StartCall(block);
                taskList.Add(startCallTask);

                if (taskList.Count >= 10)
                {
                    var completedTask = await Task.WhenAny(taskList).ConfigureAwait(false);
                    taskList.Remove(completedTask);
                }
            }

            await Task.WhenAll(taskList).ConfigureAwait(false);

            return "Ergebnis";
        }

        public static async Task<string> StartCall(IList<string> block)
        {
            return await Task.Run(() =>
            {
                Console.WriteLine($"{DateTime.Now.ToString("hh:mm:ss.ffffff")} - Task gestartet: {Thread.CurrentThread.ManagedThreadId}");
                Thread.Sleep(5000);
                Console.WriteLine($"{DateTime.Now.ToString("hh:mm:ss.ffffff")} - Task beendet: {Thread.CurrentThread.ManagedThreadId}");
                return "";
            });
        }

    }
}

Macht das gleiche, nur ohne Datenbank-Abfrage, die hier aber bzgl. der Parallelität nicht ins Gewicht fällt.

Der erste Lauf sieht so aus:


1. Lauf
10:45:56.485626 - StartCall
10:45:56.496629 - Task gestartet: 3
10:45:56.496629 - StartCall
10:45:56.496629 - Task gestartet: 4
10:45:56.496629 - StartCall
10:45:56.496629 - StartCall
10:45:56.497681 - StartCall
10:45:56.497681 - StartCall
10:45:56.497681 - StartCall
10:45:56.498631 - StartCall
10:45:56.498631 - StartCall
10:45:56.498631 - StartCall
10:45:57.497816 - Task gestartet: 5
10:45:58.496688 - Task gestartet: 6
10:45:59.498986 - Task gestartet: 7
10:46:00.498739 - Task gestartet: 8
10:46:01.497024 - Task beendet: 3
10:46:01.497024 - Task gestartet: 9
10:46:01.497024 - StartCall
10:46:01.497784 - Task beendet: 4
10:46:01.497784 - Task gestartet: 3
10:46:01.497784 - StartCall
10:46:01.497784 - Task gestartet: 4
10:46:02.499054 - Task beendet: 5
10:46:02.499780 - StartCall
10:46:02.499780 - Task gestartet: 5
10:46:02.499780 - Task gestartet: 10
10:46:03.496029 - Task gestartet: 11
10:46:03.498026 - Task beendet: 6
10:46:03.498026 - StartCall
10:46:03.498026 - Task gestartet: 6
10:46:04.499105 - Task gestartet: 12
10:46:04.499871 - Task beendet: 7
10:46:04.499871 - StartCall
10:46:04.499871 - Task gestartet: 7
10:46:05.500127 - Task beendet: 8
10:46:05.500127 - StartCall
10:46:05.500127 - Task gestartet: 8
10:46:06.498114 - Task beendet: 9
10:46:06.498114 - Task beendet: 3
10:46:06.498114 - StartCall
10:46:06.498114 - Task gestartet: 13
10:46:06.498882 - Task beendet: 4
10:46:06.498882 - StartCall
10:46:06.498882 - Task gestartet: 3
10:46:06.498882 - StartCall
10:46:06.498882 - Task gestartet: 9
10:46:07.499964 - Task beendet: 5
10:46:07.499964 - StartCall
10:46:07.499964 - Task gestartet: 5
10:46:07.500978 - Task beendet: 10
10:46:08.497151 - Task beendet: 11
10:46:08.499161 - Task beendet: 6
10:46:09.500224 - Task beendet: 12
10:46:09.501172 - Task beendet: 7
10:46:10.501283 - Task beendet: 8
10:46:11.499276 - Task beendet: 13
10:46:11.500183 - Task beendet: 9
10:46:11.500183 - Task beendet: 3
10:46:12.501308 - Task beendet: 5

Der zweite Lauf sieht wie folgt aus:


2. Lauf
10:46:12.504036 - StartCall
10:46:12.504036 - Task gestartet: 11
10:46:12.505035 - StartCall
10:46:12.505035 - Task gestartet: 7
10:46:12.505035 - StartCall
10:46:12.505035 - StartCall
10:46:12.505035 - Task gestartet: 8
10:46:12.505035 - Task gestartet: 4
10:46:12.505035 - StartCall
10:46:12.506035 - Task gestartet: 10
10:46:12.506035 - StartCall
10:46:12.506035 - Task gestartet: 3
10:46:12.507035 - StartCall
10:46:12.507035 - Task gestartet: 6
10:46:12.507035 - StartCall
10:46:12.508035 - Task gestartet: 9
10:46:12.508035 - StartCall
10:46:12.509038 - Task gestartet: 13
10:46:12.509038 - StartCall
10:46:12.509038 - Task gestartet: 12
10:46:17.505332 - Task beendet: 11
10:46:17.506163 - StartCall
10:46:17.507169 - Task gestartet: 5
10:46:17.510172 - Task beendet: 6
10:46:17.512163 - Task beendet: 12
10:46:17.505332 - Task beendet: 7
10:46:17.513162 - StartCall
10:46:17.516162 - StartCall
10:46:17.508182 - Task beendet: 10
10:46:17.518166 - Task gestartet: 11
10:46:17.506163 - Task beendet: 4
10:46:17.506163 - Task beendet: 8
10:46:17.519164 - Task gestartet: 12
10:46:17.510172 - Task beendet: 3
10:46:17.511176 - Task beendet: 9
10:46:17.511176 - Task beendet: 13
10:46:17.519164 - StartCall
10:46:17.525164 - Task gestartet: 7
10:46:17.525164 - StartCall
10:46:17.525164 - Task gestartet: 4
10:46:17.525164 - StartCall
10:46:17.528164 - Task gestartet: 3
10:46:17.529163 - StartCall
10:46:17.529163 - Task gestartet: 13
10:46:17.529163 - StartCall
10:46:17.530162 - Task gestartet: 8
10:46:17.531174 - StartCall
10:46:17.531174 - Task gestartet: 10
10:46:17.532163 - StartCall
10:46:17.532163 - Task gestartet: 9
10:46:22.511560 - Task beendet: 5
10:46:22.521448 - Task beendet: 11
10:46:22.524383 - Task beendet: 12
10:46:22.525612 - Task beendet: 7
10:46:22.527392 - Task beendet: 4
10:46:22.530460 - Task beendet: 13
10:46:22.530460 - Task beendet: 3
10:46:22.532352 - Task beendet: 8
10:46:22.533323 - Task beendet: 10
10:46:22.533323 - Task beendet: 9

Der fühlt sich ja irgendwie viel paralleliger an 😉

Der ThreadPool muss sich erst einmal warmlaufen und je nach Gesamt-Systemauslastung werden nach und nach weitere Threads im Pool gestartet, die dann zur Abarbeitung bereit stehen.

Fatalerweise, wenn die jeweiligen Aufgaben zu kurz sind, dann werden u.U. keine weiteren Threads im Pool erzeugt (darum habe ich das Sleep mal auf 5000 angehoben).

16.834 Beiträge seit 2008
vor 6 Jahren

Threads und Tasks bitte nicht vermischen.
Das ist nicht das gleiche!

Eine ThreadId sagt nicht unbedingt aus, dass dieser Task genau diesen Thread zugeordnet ist.
Tasks können einen anderen Thread als ausführenden Rahmen erhalten, wenn der Scheduler das so will und für richtig hält, zB. weil ein anderer Thread gerade frei wird.

In diesem Fall könnte es auch durchaus es sein, dass folgende beide Zeilen unterschiedliche Ids des Thread zurück liefern!

 Console.WriteLine($"{DateTime.Now.ToString("hh:mm:ss.ffffff")} - Task gestartet: {Thread.CurrentThread.ManagedThreadId}");
Thread.Sleep(5000);
Console.WriteLine($"{DateTime.Now.ToString("hh:mm:ss.ffffff")} - Task beendet: {Thread.CurrentThread.ManagedThreadId}");

Gerade wenn Code blockiert ist das nicht ungewöhntlich.
Normalerweise braucht der Task Scheduler auch keinerlei Warmup; aber die Ausführungsreihenfolge wird auch nie garantiert. Deswegen heisst es "parallel".

M
micha0827 Themenstarter:in
85 Beiträge seit 2015
vor 6 Jahren

@Sir Rufo

ich habe deine Console Ausgaben mal in meinen Code eingebaut. Sieht leider nicht gut aus 😦

11:16:18.916720 - StartCall
11:16:18.936722 - Task gestartet: 5
11:16:23.938361 - Task beendet: 5
11:16:23.967864 - StartCall
11:16:23.968370 - Task gestartet: 6
11:16:28.970153 - Task beendet: 6
11:16:28.971153 - StartCall
11:16:28.971687 - Task gestartet: 5
11:16:33.972790 - Task beendet: 5
11:16:33.977291 - StartCall
11:16:33.977291 - Task gestartet: 6
11:16:38.978068 - Task beendet: 6
11:16:38.979069 - StartCall
11:16:38.979569 - Task gestartet: 5
11:16:43.980198 - Task beendet: 5
11:16:43.981698 - StartCall
11:16:43.981698 - Task gestartet: 6
11:16:48.984788 - Task beendet: 6
11:16:48.986789 - StartCall
11:16:48.987789 - Task gestartet: 5
11:16:53.989441 - Task beendet: 5
11:16:53.991433 - StartCall
11:16:53.991934 - Task gestartet: 6
11:16:58.993569 - Task beendet: 6
11:16:58.996070 - StartCall
11:16:58.996070 - Task gestartet: 5
11:17:03.997748 - Task beendet: 5
11:17:03.999777 - StartCall
11:17:03.999777 - Task gestartet: 6
11:17:09.000116 - Task beendet: 6
11:17:09.003118 - StartCall
11:17:09.008618 - Task gestartet: 5
11:17:14.009757 - Task beendet: 5
11:17:14.012256 - StartCall
11:17:14.012256 - Task gestartet: 6
11:17:19.013424 - Task beendet: 6
11:17:19.016421 - StartCall
11:17:19.016894 - Task gestartet: 5
11:17:24.018031 - Task beendet: 5
11:17:24.024532 - StartCall
11:17:24.025075 - Task gestartet: 6
11:17:29.025943 - Task beendet: 6
11:17:29.028943 - StartCall
11:17:29.029443 - Task gestartet: 5
11:17:34.030082 - Task beendet: 5
11:17:34.033081 - StartCall
11:17:34.037581 - Task gestartet: 6
11:17:39.038538 - Task beendet: 6
11:17:39.042940 - StartCall
11:17:39.043438 - Task gestartet: 5
11:17:44.044604 - Task beendet: 5
11:17:44.049106 - StartCall
11:17:44.049599 - Task gestartet: 6
11:17:49.051731 - Task beendet: 6

16.834 Beiträge seit 2008
vor 6 Jahren

Dann stimmt was anderes bei Dir nicht; bei mir ist Sir Rufos Code durchaus parallel unterwegs.
Ohne Code von Dir können wir natürlich nicht helfen.

PS: bei Parallelität, vor allem wenn man es noch nicht 100% versteht, hilft es, wenn man den Code in einzelne Methoden aufteilt statt das so in eine Methode zu wursteln.

D
985 Beiträge seit 2014
vor 6 Jahren

@micha0827

Das ist das was ich mit

Fatalerweise, wenn die jeweiligen Aufgaben zu kurz sind, dann werden u.U. keine weiteren Threads im Pool erzeugt (darum habe ich das Sleep mal auf 5000 angehoben).

meinte.

Erhöhe mal den Sleep-Wert auf 2500, dann sieht die Welt anders aus.

In diesem Fall könnte es auch durchaus es sein, dass folgende beide Zeilen unterschiedliche Ids des Thread zurück liefern!

Jupp, das könnte durchaus passieren.

Hier eine entsprechend geänderte Variante


using System.Diagnostics;
using System.Threading;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    class Program
    {
        static async Task Main(string[] args)
        {
            var sw = new Stopwatch();
            var source = Enumerable.Range(1, 20000).Select(e => e.ToString());
            Console.WriteLine("Start");

            for (int i = 0; i < 4; i++)
            {
                Console.WriteLine($"{i+1}. Lauf");
                await DoWork(source);
            }

            Console.WriteLine("Ende");
        }

        static async Task DoWork(IEnumerable<string> source)
        {
            var sw = Stopwatch.StartNew();
            var result = await ArtikelAufteilen(source);
            sw.Stop();
            Console.WriteLine($"Completed in {sw.ElapsedMilliseconds} ms");
            Console.WriteLine("===================");
        }

        public static async Task<string> ArtikelAufteilen(IEnumerable<string> source)
        {
            var auktionen = source.ToList();

            var taskList = new List<Task<string>>();

            for (int i = 0; i < auktionen.Count; i += 1000)
            {
                var block = auktionen
                    .Skip(i)
                    .Take(1000)
                    .ToList();

                Console.WriteLine($"{DateTime.Now.ToString("hh:mm:ss.ffffff")} - StartCall {i}-{i+block.Count-1}");
                var startCallTask = StartCall(block,i);
                taskList.Add(startCallTask);

                if (taskList.Count >= 10)
                {
                    var completedTask = await Task.WhenAny(taskList).ConfigureAwait(false);
                    taskList.Remove(completedTask);
                }
            }

            await Task.WhenAll(taskList).ConfigureAwait(false);

            return "Ergebnis";
        }

        public static async Task<string> StartCall(IList<string> block, int index)
        {
            return await Task.Run(() =>
            {
                Console.WriteLine($"{DateTime.Now.ToString("hh:mm:ss.ffffff")} - Task gestartet: {index}-{index+block.Count-1}");
                Thread.Sleep(2500);
                Console.WriteLine($"{DateTime.Now.ToString("hh:mm:ss.ffffff")} - Task beendet: {index}-{index + block.Count-1}");
                return "";
            });
        }

    }
}

Die nun die jeweilige Range des Blocks anzeigt.


Start
1. Lauf
11:23:25.605654 - StartCall 0-999
11:23:25.616545 - StartCall 1000-1999
11:23:25.616545 - Task gestartet: 0-999
11:23:25.616545 - StartCall 2000-2999
11:23:25.616545 - Task gestartet: 1000-1999
11:23:25.616545 - StartCall 3000-3999
11:23:25.616545 - StartCall 4000-4999
11:23:25.617576 - StartCall 5000-5999
11:23:25.617576 - StartCall 6000-6999
11:23:25.617576 - StartCall 7000-7999
11:23:25.617576 - StartCall 8000-8999
11:23:25.617576 - StartCall 9000-9999
11:23:26.619839 - Task gestartet: 2000-2999
11:23:27.619876 - Task gestartet: 3000-3999
11:23:28.117861 - Task beendet: 1000-1999
11:23:28.117861 - Task beendet: 0-999
11:23:28.117861 - Task gestartet: 4000-4999
11:23:28.117861 - StartCall 10000-10999
11:23:28.118666 - StartCall 11000-11999
11:23:28.118666 - Task gestartet: 11000-11999
11:23:29.116910 - Task gestartet: 5000-5999
11:23:29.120912 - Task beendet: 2000-2999
11:23:29.120912 - StartCall 12000-12999
11:23:29.120912 - Task gestartet: 12000-12999
11:23:30.115901 - Task gestartet: 6000-6999
11:23:30.120902 - Task beendet: 3000-3999
11:23:30.120902 - StartCall 13000-13999
11:23:30.120902 - Task gestartet: 13000-13999
11:23:30.618920 - Task beendet: 4000-4999
11:23:30.619692 - StartCall 14000-14999
11:23:30.620692 - Task gestartet: 14000-14999
11:23:30.620692 - Task beendet: 11000-11999
11:23:30.621691 - StartCall 15000-15999
11:23:30.621691 - Task gestartet: 15000-15999
11:23:31.616999 - Task gestartet: 7000-7999
11:23:31.617750 - Task beendet: 5000-5999
11:23:31.618777 - StartCall 16000-16999
11:23:31.618777 - Task gestartet: 16000-16999
11:23:31.621988 - Task beendet: 12000-12999
11:23:31.621988 - StartCall 17000-17999
11:23:31.622724 - Task gestartet: 17000-17999
11:23:32.616910 - Task beendet: 6000-6999
11:23:32.616910 - StartCall 18000-18999
11:23:32.617770 - Task gestartet: 18000-18999
11:23:32.617770 - Task gestartet: 8000-8999
11:23:32.622031 - Task beendet: 13000-13999
11:23:32.622031 - StartCall 19000-19999
11:23:32.622809 - Task gestartet: 19000-19999
11:23:33.121996 - Task beendet: 14000-14999
11:23:33.121996 - Task gestartet: 9000-9999
11:23:33.123771 - Task beendet: 15000-15999
11:23:33.123771 - Task gestartet: 10000-10999
11:23:34.118030 - Task beendet: 7000-7999
11:23:34.119932 - Task beendet: 16000-16999
11:23:34.124078 - Task beendet: 17000-17999
11:23:35.119065 - Task beendet: 8000-8999
11:23:35.119065 - Task beendet: 18000-18999
11:23:35.123911 - Task beendet: 19000-19999
11:23:35.624123 - Task beendet: 9000-9999
11:23:35.624910 - Task beendet: 10000-10999
Completed in 10024 ms
\===================
2. Lauf
11:23:35.628850 - StartCall 0-999
11:23:35.628850 - Task gestartet: 0-999
11:23:35.628850 - StartCall 1000-1999
11:23:35.629851 - StartCall 2000-2999
11:23:35.630854 - StartCall 3000-3999
11:23:35.631852 - Task gestartet: 3000-3999
11:23:35.629851 - Task gestartet: 1000-1999
11:23:35.630854 - Task gestartet: 2000-2999
11:23:35.631852 - StartCall 4000-4999
11:23:35.632850 - Task gestartet: 4000-4999
11:23:35.632850 - StartCall 5000-5999
11:23:35.632850 - Task gestartet: 5000-5999
11:23:35.632850 - StartCall 6000-6999
11:23:35.635852 - Task gestartet: 6000-6999
11:23:35.635852 - StartCall 7000-7999
11:23:35.635852 - Task gestartet: 7000-7999
11:23:35.636851 - StartCall 8000-8999
11:23:35.636851 - StartCall 9000-9999
11:23:35.636851 - Task gestartet: 9000-9999
11:23:36.624160 - Task gestartet: 8000-8999
11:23:38.131201 - Task beendet: 0-999
11:23:38.131201 - StartCall 10000-10999
11:23:38.132931 - Task gestartet: 10000-10999
11:23:38.132931 - Task beendet: 1000-1999
11:23:38.133931 - StartCall 11000-11999
11:23:38.134931 - Task gestartet: 11000-11999
11:23:38.132931 - Task beendet: 3000-3999
11:23:38.136932 - StartCall 12000-12999
11:23:38.138931 - Task gestartet: 12000-12999
11:23:38.138931 - Task beendet: 7000-7999
11:23:38.143931 - StartCall 13000-13999
11:23:38.144932 - Task gestartet: 13000-13999
11:23:38.136932 - Task beendet: 6000-6999
11:23:38.150933 - StartCall 14000-14999
11:23:38.151933 - Task gestartet: 14000-14999
11:23:38.133931 - Task beendet: 4000-4999
11:23:38.152942 - StartCall 15000-15999
11:23:38.134931 - Task beendet: 2000-2999
11:23:38.135931 - Task beendet: 5000-5999
11:23:38.153933 - StartCall 16000-16999
11:23:38.152942 - Task gestartet: 15000-15999
11:23:38.154933 - Task gestartet: 16000-16999
11:23:38.139932 - Task beendet: 9000-9999
11:23:38.154933 - StartCall 17000-17999
11:23:38.156934 - StartCall 18000-18999
11:23:38.156934 - Task gestartet: 18000-18999
11:23:38.156934 - Task gestartet: 17000-17999
11:23:39.125194 - Task beendet: 8000-8999
11:23:39.125194 - StartCall 19000-19999
11:23:39.126021 - Task gestartet: 19000-19999
11:23:40.634207 - Task beendet: 10000-10999
11:23:40.637297 - Task beendet: 11000-11999
11:23:40.641242 - Task beendet: 12000-12999
11:23:40.648222 - Task beendet: 13000-13999
11:23:40.653263 - Task beendet: 14000-14999
11:23:40.656265 - Task beendet: 15000-15999
11:23:40.657081 - Task beendet: 16000-16999
11:23:40.659299 - Task beendet: 18000-18999
11:23:40.659299 - Task beendet: 17000-17999
11:23:41.628202 - Task beendet: 19000-19999
Completed in 6001 ms
\===================
3. Lauf
11:23:41.634191 - StartCall 0-999
11:23:41.635158 - Task gestartet: 0-999
11:23:41.635158 - StartCall 1000-1999
11:23:41.636042 - Task gestartet: 1000-1999
11:23:41.636042 - StartCall 2000-2999
11:23:41.636042 - Task gestartet: 2000-2999
11:23:41.637045 - StartCall 3000-3999
11:23:41.637045 - Task gestartet: 3000-3999
11:23:41.637045 - StartCall 4000-4999
11:23:41.639078 - Task gestartet: 4000-4999
11:23:41.640043 - StartCall 5000-5999
11:23:41.640043 - Task gestartet: 5000-5999
11:23:41.640043 - StartCall 6000-6999
11:23:41.641044 - Task gestartet: 6000-6999
11:23:41.641044 - StartCall 7000-7999
11:23:41.642046 - Task gestartet: 7000-7999
11:23:41.642046 - StartCall 8000-8999
11:23:41.642046 - Task gestartet: 8000-8999
11:23:41.643043 - StartCall 9000-9999
11:23:41.646561 - Task gestartet: 9000-9999
11:23:44.136270 - Task beendet: 0-999
11:23:44.136270 - StartCall 10000-10999
11:23:44.137191 - Task gestartet: 10000-10999
11:23:44.137191 - Task beendet: 1000-1999
11:23:44.138122 - Task beendet: 2000-2999
11:23:44.138122 - StartCall 11000-11999
11:23:44.139123 - StartCall 12000-12999
11:23:44.139123 - Task beendet: 3000-3999
11:23:44.139123 - Task gestartet: 12000-12999
11:23:44.139123 - Task gestartet: 11000-11999
11:23:44.140122 - StartCall 13000-13999
11:23:44.140122 - Task gestartet: 13000-13999
11:23:44.141123 - Task beendet: 5000-5999
11:23:44.142123 - Task beendet: 6000-6999
11:23:44.141123 - Task beendet: 4000-4999
11:23:44.142123 - StartCall 14000-14999
11:23:44.143123 - StartCall 15000-15999
11:23:44.143123 - Task beendet: 7000-7999
11:23:44.144122 - Task beendet: 8000-8999
11:23:44.143123 - StartCall 16000-16999
11:23:44.147128 - Task beendet: 9000-9999
11:23:44.143123 - Task gestartet: 15000-15999
11:23:44.148123 - Task gestartet: 16000-16999
11:23:44.148123 - StartCall 17000-17999
11:23:44.143123 - Task gestartet: 14000-14999
11:23:44.150122 - Task gestartet: 17000-17999
11:23:44.150122 - StartCall 18000-18999
11:23:44.151123 - StartCall 19000-19999
11:23:44.151123 - Task gestartet: 19000-19999
11:23:44.151123 - Task gestartet: 18000-18999
11:23:46.638433 - Task beendet: 10000-10999
11:23:46.641498 - Task beendet: 11000-11999
11:23:46.641498 - Task beendet: 12000-12999
11:23:46.642204 - Task beendet: 13000-13999
11:23:46.650359 - Task beendet: 15000-15999
11:23:46.650359 - Task beendet: 16000-16999
11:23:46.651225 - Task beendet: 14000-14999
11:23:46.651225 - Task beendet: 17000-17999
11:23:46.652302 - Task beendet: 19000-19999
11:23:46.652302 - Task beendet: 18000-18999
Completed in 5021 ms
\===================
4. Lauf
11:23:46.656202 - StartCall 0-999
11:23:46.656202 - Task gestartet: 0-999
11:23:46.658205 - StartCall 1000-1999
11:23:46.659207 - Task gestartet: 1000-1999
11:23:46.659207 - StartCall 2000-2999
11:23:46.660205 - StartCall 3000-3999
11:23:46.660205 - Task gestartet: 2000-2999
11:23:46.660205 - StartCall 4000-4999
11:23:46.661220 - Task gestartet: 3000-3999
11:23:46.661220 - Task gestartet: 4000-4999
11:23:46.662204 - StartCall 5000-5999
11:23:46.663214 - Task gestartet: 5000-5999
11:23:46.663214 - StartCall 6000-6999
11:23:46.663214 - Task gestartet: 6000-6999
11:23:46.663214 - StartCall 7000-7999
11:23:46.664204 - Task gestartet: 7000-7999
11:23:46.664204 - StartCall 8000-8999
11:23:46.665205 - Task gestartet: 8000-8999
11:23:46.665205 - StartCall 9000-9999
11:23:46.665205 - Task gestartet: 9000-9999
11:23:49.159553 - Task beendet: 0-999
11:23:49.159553 - StartCall 10000-10999
11:23:49.161283 - Task gestartet: 10000-10999
11:23:49.161283 - Task beendet: 1000-1999
11:23:49.161283 - StartCall 11000-11999
11:23:49.161283 - Task gestartet: 11000-11999
11:23:49.162331 - Task beendet: 2000-2999
11:23:49.162331 - StartCall 12000-12999
11:23:49.162331 - Task gestartet: 12000-12999
11:23:49.163306 - Task beendet: 4000-4999
11:23:49.163306 - Task beendet: 3000-3999
11:23:49.163306 - StartCall 13000-13999
11:23:49.163306 - StartCall 14000-14999
11:23:49.164283 - Task beendet: 5000-5999
11:23:49.164283 - Task gestartet: 14000-14999
11:23:49.165284 - Task beendet: 7000-7999
11:23:49.165284 - StartCall 15000-15999
11:23:49.165284 - Task beendet: 6000-6999
11:23:49.164283 - Task gestartet: 13000-13999
11:23:49.166282 - Task beendet: 8000-8999
11:23:49.167283 - Task beendet: 9000-9999
11:23:49.166282 - Task gestartet: 15000-15999
11:23:49.167283 - StartCall 16000-16999
11:23:49.168281 - StartCall 17000-17999
11:23:49.169282 - StartCall 18000-18999
11:23:49.169282 - Task gestartet: 17000-17999
11:23:49.170283 - Task gestartet: 18000-18999
11:23:49.168281 - Task gestartet: 16000-16999
11:23:49.171283 - StartCall 19000-19999
11:23:49.172282 - Task gestartet: 19000-19999
11:23:51.662635 - Task beendet: 11000-11999
11:23:51.663363 - Task beendet: 12000-12999
11:23:51.662635 - Task beendet: 10000-10999
11:23:51.666635 - Task beendet: 14000-14999
11:23:51.668645 - Task beendet: 13000-13999
11:23:51.669364 - Task beendet: 15000-15999
11:23:51.672399 - Task beendet: 16000-16999
11:23:51.672399 - Task beendet: 18000-18999
11:23:51.673362 - Task beendet: 19000-19999
11:23:51.672399 - Task beendet: 17000-17999
Completed in 5020 ms
\===================
Ende

M
micha0827 Themenstarter:in
85 Beiträge seit 2015
vor 6 Jahren

Mein Code sieht aktuell so aus:


public async Task<string> ArtikelAufteilen()
        {
            using (baygraphEntities db = new baygraphEntities())
            {
                var auktionen = (from a in db.bayEbayTracking
                                 join b in db.bayEbayCountry on a.globalID equals b.id
                                 where a.aktiv != false && a.abgelaufen == null
                                 orderby a.id
                                 select new { bayEbayTracking = a, bayEbayCountry = b }).ToList();

                List<Task<string>> taskList = new List<Task<string>>();
                
                for (int i = 0; i < auktionen.Count; i += 1000)
                {
                    var block = auktionen
                        .Select(art => new Artikel()
                        {
                            id = art.bayEbayTracking.id,
                            artikelnummer = art.bayEbayTracking.artikelnummer,
                            keywords = art.bayEbayTracking.keywords,
                            GlobalID = art.bayEbayCountry.GlobalID
                        })
                        .Skip(i)
                        .Take(1000)
                        .ToList();

                    Console.WriteLine($"{DateTime.Now.ToString("hh:mm:ss.ffffff")} - StartCall");
                    var StartCallTask = StartCall(block);
                    taskList.Add(StartCallTask);

                    if (taskList.Count >= 10)
                    {
                        var completedTask = await Task.WhenAny(taskList).ConfigureAwait(false);
                        taskList.Remove(completedTask);
                    }
                    await Task.WhenAll(taskList).ConfigureAwait(false);
                }

                return "Ergebnis";
            }
        }

        private async Task<string> StartCall(List<Artikel> block)
        {
            return await Task.Run(() =>
            {
                Console.WriteLine($"{DateTime.Now.ToString("hh:mm:ss.ffffff")} - Task gestartet: {Thread.CurrentThread.ManagedThreadId}");
                Thread.Sleep(5000);
                Console.WriteLine($"{DateTime.Now.ToString("hh:mm:ss.ffffff")} - Task beendet: {Thread.CurrentThread.ManagedThreadId}");
                return "";
            });
        }

der Aufruf so:


static void Main(string[] args)
        {
Start().Wait();
}

public static async Task Start()
        {
            Stopwatch watches = new Stopwatch();
            watches.Start();

            ApiFindingCall call = new ApiFindingCall();
            
            string result = await call.ArtikelAufteilen();

            watches.Stop();
            
        }

eine Async Main Methode wurde angemeckert 😃

D
985 Beiträge seit 2014
vor 6 Jahren

Dann ändere deinen Code mal


    public async Task<string> ArtikelAufteilen()
        {
            using (baygraphEntities db = new baygraphEntities())
            {
                var auktionen = (from a in db.bayEbayTracking
                                 join b in db.bayEbayCountry on a.globalID equals b.id
                                 where a.aktiv != false && a.abgelaufen == null
                                 orderby a.id
                                 select new { bayEbayTracking = a, bayEbayCountry = b }).ToList();

                List<Task<string>> taskList = new List<Task<string>>();

                for (int i = 0; i < auktionen.Count; i += 1000)
                {
                    var block = auktionen
                        .Select(art => new Artikel()
                        {
                            id = art.bayEbayTracking.id,
                            artikelnummer = art.bayEbayTracking.artikelnummer,
                            keywords = art.bayEbayTracking.keywords,
                            GlobalID = art.bayEbayCountry.GlobalID
                        })
                        .Skip(i)
                        .Take(1000)
                        .ToList();

                    Console.WriteLine($"{DateTime.Now.ToString("hh:mm:ss.ffffff")} - StartCall");
                    var StartCallTask = StartCall(block);
                    taskList.Add(StartCallTask);

                    if (taskList.Count >= 10)
                    {
                        var completedTask = await Task.WhenAny(taskList).ConfigureAwait(false);
                        taskList.Remove(completedTask);
                    }
                    // nicht in der Schleife warten
                    // await Task.WhenAll(taskList).ConfigureAwait(false);
                }
                // sondern hier, ausserhalb der Schleife
                await Task.WhenAll(taskList).ConfigureAwait(false);

                return "Ergebnis";
            }
        }

16.834 Beiträge seit 2008
vor 6 Jahren

Grundlegend gut (bzw. besser) strukturiert könnte der Code einfach so aussehen:

namespace ConsoleApp2
{
    class Program
    {
        static void Main(string[] args)
        {
            MainAsync(args).GetAwaiter().GetResult();
        }

        static async Task MainAsync(string[] args)
        {

            Console.WriteLine("Start");

            ArtikelRepository repository = new ArtikelRepository();

            MyBusinessLayer myBusiness = new MyBusinessLayer(repository);
            var result = await myBusiness.LoadAllArticlesAsync();

            Console.WriteLine("Total: " + result);

            Console.ReadKey();
        }
    }

    public class MyBusinessLayer
    {
        private ArtikelRepository _artikelRepository;
        public MyBusinessLayer(ArtikelRepository repository)
        {
            _artikelRepository = repository;
        }

        public Task<int> LoadAllArticlesAsync()
        {

            var chunkSize = 500;
            var total = _artikelRepository.GetCount();

            var pages = (int)Math.Ceiling(total * 1.0 / chunkSize);

            int result = 0;
            Parallel.For(0, pages, async i =>
            {
                var skip = ((int)i * chunkSize);
                var take = chunkSize;

                Console.WriteLine($"[START] {skip}-{skip + chunkSize} ");
                // geht an der stelle aber so nicht, wenn das Repository hier keinen Thread-Safe context hat; dann isoliert umsetzen
                var data = await _artikelRepository.GetArtikelChunkAsync(skip, take);

                int sum = await DoAsync(data);

                Interlocked.Add(ref result, sum);

                Console.WriteLine($"[END] {skip}-{skip + chunkSize} (Sum: {sum}) ");
            });

            return Task.FromResult(result);
        }

        public Task<int> DoAsync(IList<int> block)
        {
            return Task.FromResult(block.Sum());
        }
    }

    public class ArtikelRepository
    {
        public IList<int> _demoDaten;

        public ArtikelRepository(/* hier den Datenbankkontext gemäß Repository Pattern übergen */)
        {
            _demoDaten = Enumerable.Range(0, 20000).ToList();
        }

        public int GetCount()
        {
            return _demoDaten.Count;
        }

        public Task<IList<int>> GetArtikelChunkAsync(int skip, int take)
        {
            // normalerweise eben asynchrone Datenbank-Methoden verwenden
            IList<int> data = _demoDaten.Skip(skip).Take(take).ToList();
            return Task.FromResult(data);
        }
    }
}

Start
[START] 0-500
[START] 1000-1500
[START] 3000-3500
[START] 4000-4500
[START] 2000-2500
[START] 5000-5500
[START] 6000-6500
[START] 7000-7500
[START] 8000-8500
[START] 9000-9500
[END] 0-500 (Sum: 124750)
[END] 8000-8500 (Sum: 4124750)
[END] 5000-5500 (Sum: 2624750)
[END] 2000-2500 (Sum: 1124750)
[END] 7000-7500 (Sum: 3624750)
[START] 10000-10500
[END] 4000-4500 (Sum: 2124750)
[START] 500-1000
[END] 10000-10500 (Sum: 5124750)
[START] 10500-11000
[START] 5500-6000
[START] 7500-8000
[END] 5500-6000 (Sum: 2874750)
[START] 6500-7000
[END] 10500-11000 (Sum: 5374750)
[START] 11000-11500
[START] 2500-3000
[END] 6000-6500 (Sum: 3124750)
[END] 500-1000 (Sum: 374750)
[START] 4500-5000
[END] 6500-7000 (Sum: 3374750)
[END] 7500-8000 (Sum: 3874750)
[END] 9000-9500 (Sum: 4624750)
[END] 11000-11500 (Sum: 5624750)
[START] 11500-12000
[START] 15500-16000
[START] 16000-16500
[START] 12500-13000
[START] 9500-10000
[START] 12000-12500
[END] 12000-12500 (Sum: 6124750)
[START] 16500-17000
[END] 16500-17000 (Sum: 8374750)
[START] 17000-17500
[END] 17000-17500 (Sum: 8624750)
[START] 17500-18000
[END] 17500-18000 (Sum: 8874750)
[START] 18000-18500
[START] 14500-15000
[END] 15500-16000 (Sum: 7874750)
[START] 19000-19500
[END] 19000-19500 (Sum: 9624750)
[START] 19500-20000
[END] 19500-20000 (Sum: 9874750)
[START] 3500-4000
[START] 15000-15500
[END] 3500-4000 (Sum: 1874750)
[START] 1500-2000
[END] 1500-2000 (Sum: 874750)
[END] 15000-15500 (Sum: 7624750)
[END] 18000-18500 (Sum: 9124750)
[END] 4500-5000 (Sum: 2374750)
[START] 14000-14500
[END] 14000-14500 (Sum: 7124750)
[END] 3000-3500 (Sum: 1624750)
[END] 11500-12000 (Sum: 5874750)
[END] 1000-1500 (Sum: 624750)
[START] 13000-13500
[END] 13000-13500 (Sum: 6624750)
[END] 2500-3000 (Sum: 1374750)
[START] 18500-19000
[END] 18500-19000 (Sum: 9374750)
[END] 16000-16500 (Sum: 8124750)
[END] 14500-15000 (Sum: 7374750)
[END] 9500-10000 (Sum: 4874750)
[START] 8500-9000
[START] 13500-14000
[END] 8500-9000 (Sum: 4374750)
[END] 13500-14000 (Sum: 6874750)
[END] 12500-13000 (Sum: 6374750)
Total: 199990000

Und sowas:

static void Main(string[] args)
        {
Start().Wait();
}

bitte erst gar nicht angewöhnen. Das ist absolut falsch.
.Wait() und .Result ist genau das, was man nicht verwenden sollte, wenn möglich.
Ein absolutes Indiz auf falsche Handhabung von Tasks!

Ich fürchte micha hat nun Code, der funktioniert - den er aber nicht verstanden hat 😉

M
micha0827 Themenstarter:in
85 Beiträge seit 2015
vor 6 Jahren

jetzt siehts bei mir auch besser aus 😃

11:56:53.680609 - StartCall
11:56:53.755119 - StartCall
11:56:53.755618 - Task gestartet: 5
11:56:53.757618 - Task gestartet: 7
11:56:53.758119 - StartCall
11:56:53.758619 - Task gestartet: 6
11:56:53.760619 - StartCall
11:56:53.760619 - Task gestartet: 8
11:56:53.761619 - StartCall
11:56:53.762619 - StartCall
11:56:53.764620 - StartCall
11:56:53.766620 - StartCall
11:56:53.768620 - StartCall
11:56:53.771121 - StartCall
11:56:54.712240 - Task gestartet: 9
11:56:55.227806 - Task gestartet: 10
11:56:56.211931 - Task gestartet: 11
11:56:57.212558 - Task gestartet: 12
11:56:58.212186 - Task gestartet: 13
11:56:58.756756 - Task beendet: 5
11:56:58.758756 - Task beendet: 7
11:56:58.758756 - Task gestartet: 7
11:56:58.759756 - Task beendet: 6
11:56:58.760256 - StartCall
11:56:58.760256 - Task gestartet: 6
11:56:58.761756 - Task beendet: 8
11:56:58.762756 - StartCall
11:56:58.762756 - Task gestartet: 8
11:56:58.765757 - StartCall
11:56:58.770257 - StartCall
11:56:58.778259 - Task gestartet: 5
11:56:59.711878 - Task gestartet: 14
11:56:59.713377 - Task beendet: 9
11:56:59.716378 - StartCall
11:56:59.716878 - Task gestartet: 9
11:57:00.228443 - Task beendet: 10
11:57:00.232444 - StartCall
11:57:00.232944 - Task gestartet: 10
11:57:01.212568 - Task beendet: 11
11:57:01.216069 - StartCall
11:57:01.217569 - Task gestartet: 15
11:57:02.214231 - Task beendet: 12
11:57:02.221232 - StartCall
11:57:02.226233 - Task gestartet: 11
11:57:03.219359 - Task beendet: 13
11:57:03.766929 - Task beendet: 7
11:57:03.767929 - Task beendet: 6
11:57:03.770429 - Task beendet: 8
11:57:03.786431 - Task beendet: 5
11:57:04.719551 - Task beendet: 14
11:57:04.725051 - Task beendet: 9
11:57:05.240617 - Task beendet: 10
11:57:06.226197 - Task beendet: 15
11:57:07.228825 - Task beendet: 11

Danke
Michael

D
985 Beiträge seit 2014
vor 6 Jahren

eine Async Main Methode wurde angemeckert 🙂

Logisch, die gibt es auch erst mit C# 7.1 und diese Nebenversion musst du explizit bei den Build-Optionen angeben 😁

Projekt-Eigenschaften / Build / Erweitert... (Button) / Allgemein - Sprachversion: Aktuelle C#-Nebenversion

16.834 Beiträge seit 2008
vor 6 Jahren

Oder eben ganz einfach, wie genannt und empfohlen:

  static void Main(string[] args)
        {
            MainAsync(args).GetAwaiter().GetResult();
        }

P
1.090 Beiträge seit 2011
vor 6 Jahren

Mal eine Andere Sache hast du denn schon mal geschaut ob 40 x (20.000 / 500) die Query mit Skip und Take, in je einer Tasks wirklich schneller ist, als sich die 20.000 Datensätze direkt mit einer Abfrage zurück zu geben? Und nur diese Abfrage in einer Task auszuführen.

Sollte man mal gelesen haben:

Clean Code Developer
Entwurfsmuster
Anti-Pattern

16.834 Beiträge seit 2008
vor 6 Jahren

Palin, wie bereits weiter oben zu lesen ist macht er das gar nicht.
Durch das ToList werden alle 20.000 Einträge in einem Query geladen (ADO.NET => ToList); die nachträgliche Verarbeitung im RAM ist dann parallel.

D
985 Beiträge seit 2014
vor 6 Jahren

Durch das ToList werden alle 20.000 Einträge in einem Query geladen

Was allerdings in Zukunft auch zu einem Problem werden kann, wenn die Anzahl der Datensätze steigt.

Ich möchte gern die 20 Aufrufe in einer Anwendung bündeln (oder mehr, je nachdem wie die DB wächst) ...

M
micha0827 Themenstarter:in
85 Beiträge seit 2015
vor 6 Jahren

Ich fürchte micha hat nun Code, der funktioniert - den er aber nicht verstanden hat 😉

Durchaus wahr, aber ich bin schon weiter als vor 3 Monaten, und auch jetzt wird es weitergehen.

Was allerdings in Zukunft auch zu einem Problem werden kann, wenn die Anzahl der Datensätze steigt.

Stimmt, aber auch bei 200.000 oder 2.000.000 muss ich trotzdem alle Datensätze abarbeiten.

Michael

D
985 Beiträge seit 2014
vor 6 Jahren

Stimmt, aber auch bei 200.000 oder 2.000.000 muss ich trotzdem alle Datensätze abarbeiten.

Ist mir schon klar, allerdings wirst du mit dem aktuellen Ansatz hier evtl. an die Grenze deines RAMs kommen.

Du solltest dir also eine andere Vorgehensweise überlegen, damit das eben nicht passieren kann.

16.834 Beiträge seit 2008
vor 6 Jahren

=>

Das, was Du da in Tasks aufteilst, sollte - sofern man das hier sieht - keine Aktion auf den Db Context erzeugen, sondern sich nur auf Daten beziehen, die eh im RAM liegen.
Damit kann man mit einem PLINQ Query alles sehr schnell und mit wenig Code parallelisieren.
Hat den Nachteil: bei vielen Daten gibt es eine OutOfMemoryException.

T
2.224 Beiträge seit 2008
vor 6 Jahren

@micha0827
Wie groß ist den eigentlich der Geschwidnigkeitunterscheid zu einem Durchlauf ohne Tasks?
Lohnt sich den dieser Ansatz überhaupt oder machen alle nur Trockentests oder wirklich den Vorteil messen zu können?
Geht mir nur darum, dass die Lösung auch funktioniert im Sinne der Zeitersparnis.
Ansonsten verbrennen hier alle nur Zeit.

Wenn die Daten schon im RAM liegen und nichts weiter nachgeladen werden muss, kann die Auswertung nur marginal schneller sein oder werden die Artikel nochmal nachgeladen?
Dann wäre der Ansatz noch sinnvoll, aber wenn alles im RAM liegt kann ich mir nicht vorstellen, dass dort mehr Performance rausgeholt werden kann.

Meine weitere Frage wäre auch, wie das Speicherproblem gelöst werden soll bzw. was mit den Daten eigentlich passieren soll?
So viele Daten einzuladen kann ja bekanntlich knallen.
Nur wenn die Daten z.B. auf einer Seite per Paging angezeigt werden sollen, brauchst du nicht alle Daten einladen.
Dann wäre eher Lazy Loading mit Paging sinnvoll um die Performace + Speicherverbrauch gering zu halten.

T-Virus

Developer, Developer, Developer, Developer....

99 little bugs in the code, 99 little bugs. Take one down, patch it around, 117 little bugs in the code.

16.834 Beiträge seit 2008
vor 6 Jahren

T-Virus; gerade wenn die Daten im RAM liegen kann dessen Verarbeitung deutlich performanter sein. Selbst das einfache Mappen von Daten von Klasse A auf eine Klasse B läuft parallel i.d.R. immer schneller.
Problematisch wird es jedoch, wenn parallele Aufgaben sich cascadieren; dann gibt es einen negativen Effekt.

Ein Query zu parallelisieren, der mehrere Full-Table-Scans auslöst, macht hingegen keinen Sinn.

T
2.224 Beiträge seit 2008
vor 6 Jahren

@Abt
Abt hängt aber auch von einigen Faktoren ab.
Wenn ich nur einen "kleinen" Datenbestand verarbeite, brauche ich keine Tasks.
Wenn hier die Artikel aber noch nachgeladen werden, dann wäre es schon sinnvoll, dies auf Tasks zu verteilen.

Aber es steht halt noch die Frage im Raum, was das Ziel des Ganzen wird.
Wenn es nur um einfache Datenanzeige geht, kann man dies auch anderst lösen.
Ich bin der Meinung, dass das eigentliche Problem, was diesen Prozess benötigt, nicht bekannt ist.
Somit kann es also sein, dass wir Performance Tipps geben, die vielleicht nicht mal zielführend sind.

Auch wenn die Lösungen an sich korrekt und super sind, habe ich das Gefühl, dass hier ein anderer Ansatz besser wäre.
Aber dies ist nur eine Vermutung, die ich ohne das eigentliche Ziel des Ganzen nicht kenne.
Gerade wenn das Speicherproblem in Zukunft auftreten kann, wovon man bei jetzt schon 20.000 Auktionen ausgehen kann, sollte man eine andere Lösung in Betracht ziehen.

T-Virus

Developer, Developer, Developer, Developer....

99 little bugs in the code, 99 little bugs. Take one down, patch it around, 117 little bugs in the code.

16.834 Beiträge seit 2008
vor 6 Jahren

Jetzt vermischt Du aber wieder Nachladen mit Daten im RAM.
Selbst bei einer kleinen Enumeration kann die Parallelität mehrere hundert Prozent an Geschwindigkeit bringen.

Die Frage ist - neben den Prozenten - ob die erhöhte Code-Komplexität aber in der totalen Zeitersparnis in Form von Millisekunden was bringt.
Wenn man sich 300% spart aber das in Summe nur 20ms ausmacht, dann ist der Gewinn irrelevant für die Realität.

T
2.224 Beiträge seit 2008
vor 6 Jahren

@Abt
Ist natürlich auch ein Aspekt, den man beachten sollte.
Gerade wenn der Aufwand, also auch die Umsetzungsdauer noch hoch ist, wäre dies auch kein großer Vorteil.
Wenn es aber bei einer größeren Datenmenge doch Zeit erspart und diese Auswertung häufig gefahren wird kann sowohl die Komplexität als auch die Dauer der Umsetzung einen gewissen Anteil haben.

Hier würde mih aber der aktuelle Zugewinn in der Performance schon interessieren.
Wenn der OP schon bessere Resultate als vor 3 Monate hat, dann dürfte hier das Plus schon recht groß sein.

T-Virus

Developer, Developer, Developer, Developer....

99 little bugs in the code, 99 little bugs. Take one down, patch it around, 117 little bugs in the code.

M
micha0827 Themenstarter:in
85 Beiträge seit 2015
vor 6 Jahren

wie bereits in einem vorherigen Kommentar geschrieben, läuft das ganze momentan in 20 Consolen Anwendungen, die Parallel laufen. Die machen 160.000 Api Calls mit Datenauswertung in zusammen 3 Min. Mein Ziel ist das ganze in eine Anwendung zu packen. Erste Tests zeigen momentan ein Problem mit zu vielen Connections (geöffnete Ports). Beim Server konnte ich das Problem mit der Erhöhung der MaxUserPorts in der Registry lösen, wie das bei Azure WebJobs geht weiss ich noch nicht. Eventuell doch wieder über mehrere Jobs verteilen, aber da weiss ich nicht wie man die synchronisiert.

Michael

T
2.224 Beiträge seit 2008
vor 6 Jahren

@micha0827
Den Part hatte ich leider überlesen.
Dein aktueller Ansatz dürfte hier, wenn das Programm auch 20x mal direkt gestartet wird, auch Probleme bekommen.
Hauptsächlich Speicherprobleme, die dann nur per einzel Aufruf oder als Batches gelöst werden könnten.

Wäre mit dem jetzigen Ansatz aber besser, da du nun nicht 20x die Auktionen in dem RAM laden musst, sondern nur einmal.
Entsprechend dürfte der aktuelle Ansatz der bessere sein als 20x das Programm mit entsprechenden Parametern zu starten.

T-Virus

Developer, Developer, Developer, Developer....

99 little bugs in the code, 99 little bugs. Take one down, patch it around, 117 little bugs in the code.

M
micha0827 Themenstarter:in
85 Beiträge seit 2015
vor 6 Jahren

Ich weiss das der aktuelle Ansatz besser ist. Aber die Anwendung 20x zu starten funktioniert halt aktuell, der neue Ansatz noch nicht.

Ich mach das aber nicht aus Speicherproblemen, aktuell benutze ich nicht einmal einen Bruchteil von den 28/32Gb, der Engpass ist eher die CPU.

Michael