Laden...

Merkwürdiges Verhalten bei Eintragung vieler Datensätze in die Datenbank

Erstellt von inflames2k vor 6 Jahren Letzter Beitrag vor 6 Jahren 1.208 Views
inflames2k Themenstarter:in
2.298 Beiträge seit 2010
vor 6 Jahren
Merkwürdiges Verhalten bei Eintragung vieler Datensätze in die Datenbank

verwendetes Datenbanksystem: MSSQL Server (2012)

Hallo,

ein bestehendes Programm wurde durch mich um eine Funktion erweitert, die eine große Menge an Artikeln im Datenbestand umbewerten soll. Ich ermittle die Datensätze aus der Datenbank und schreibe diese anschließend in einer Schleife mit der neuen Bewertung zurück.

Der Ablauf findet in einem Extra-Thread statt, nach dessen Fertigstellung ich eine Meldung ausgebe. Nach Fertigstellung sollten aus meiner Sicht alle Daten bereits in der entsprechenden Datenbanktabelle stehen. - Die Speicherung erfolgt über eine gespeicherte Prozedur der jeder Datensatz einzeln übergeben wird.

Beobachtet habe ich nun das wenn mein Programm bereits fertig ist mit dem Schreiben der Daten, die Tabelle noch nicht alle Datensätze enthält und sich Schrittweise füllt. Ich frage mich nun warum das so ist. Bei großen Datenmengen kann das im Produktiv Einsatz nämlich schon ein paar Probleme hervor rufen.

Mein Code für die Umbewertung sieht wie folgt aus:


private void ExecuteDownGrade(int downGradeID)
{
    ThreadPool.QueueUserWorkItem(new WaitCallback(delegate(Object state)
    {
        this._downGradeState.UpdateState("Ermittlung der abzuwertenden Artikel...");
        DataTable dtDownGradeArticles = null;

        try
        {
            dtDownGradeArticles = this.TypedController.GetDownGradeArticles(this._downGradeInfo);
        }
        catch
        {
            this.DownGradeFinished(false, true);
        }

        List<string> failedDownGrades = new List<string>();

        if (dtDownGradeArticles != null)
        {
            int currentArticle = 0;
            foreach (DataRow row in dtDownGradeArticles.Rows)
            {
                if (!this._downGradeState.IsCanceled)
                {
                    currentArticle ++;
                    this._downGradeState.UpdateState("Abwertung Artikel{0} von {1}", currentArticle, dtDownGradeArticles.Rows.Count);

                    try
                    {
                        this.TypedController.DownGradeArticle(row, this._downGradeInfo, downGradeID);
                    }
                    catch
                    {
                        // add failed downgrade to list
                        failedDownGrades.Add(row["ArticleID"].ToString());
                    }
                }
            }
        }

        this.DownGradeFinished(false, failedDownGrades.Count > 0);
    }));
}

Die Methode DownGradeArticle macht nichts anderes, als die Daten für den Artikel anzupassen und über die gespeicherte Prozedur an die Datenbank zu übergeben. Dies geschieht direkt ohne einen weiteren Thread. Die Prozedur Commited die Eintragungen auch vor Beendigung dieser.

Insofern erschließt sich mir nicht, wieso der Thread fertig ist aber noch nicht alle Daten in der Tabelle stehen sondern diese sich nach und nach füllt.

Woran liegt das und gibt es eine Möglichkeit das Verhalten anzupassen, dass der Prozeduraufruf auch tatsächlich erst zurück kommt, wenn der Datensatz gespeichert ist? Das ganze dann bitte aber auch ohne Warteschleifen in die Prozedur zu bauen.

Als Randinformation noch:
Die Prozedur ruft intern weitere gespeicherte Prozeduren auf und verteilt bestimmte Daten damit auf unterschiedliche Tabellen. Aber alle Aktionen innerhalb der Prozeduren laufen im gleichen Thread (weiß auch garnicht ob Multithreaded in gespeicherten Prozeduren überhaupt möglich wäre).

Wissen ist nicht alles. Man muss es auch anwenden können.

PS Fritz!Box API - TR-064 Schnittstelle | PS EventLogManager |

16.834 Beiträge seit 2008
vor 6 Jahren

Ein QueueUserWorkItem ist - wie der Name schon sagt - ein Queue Item.

Du fügst an dieser Stelle das Item nur hinzu (es sieht aus Code-sicht so aus, als sei es fertig) und liegt in der Queue. Jedenfalls wäre das nun meine Vermutung aus der Glaskugel 😃
Der Scheduler kümmert sich dann selbst um die Ausführung.

Dein Code ist glaube ich so weder gewollt, noch qualitativ gut 😉
Besser wäre eine Art TPL Pipeline, in der Du Datensätze einkippst und dann Stufe für Stufe weiterreichst und bearbeitest.

inflames2k Themenstarter:in
2.298 Beiträge seit 2010
vor 6 Jahren

Ja, aber mein Event wird doch erst ausgeführt wenn der Code fertig ausgeführt wurde. Wenn dem nicht so ist, muss ich ja die letzten 7 Jahre nur quatsch produziert haben.

Das der Code so noch nicht sauber ist, ist mir klar. - Aber nichts desto trotz, sollte das Ereignis eben doch erst ausgeführt werden wenn der Code und die Schleife fertig ausgeführt wurden. Steht immerhin am Ende.

PS: Auf Threads / die Threadqueue bin ich beschränkt, da es sich um eine .NET 2.0 Anwendung handelt, deren Migration auf ein höheres Framework nicht vorgesehen ist.

EDIT: Bzgl. deiner Aussage der Code wäre so nicht gewollt.

Mein Ziel war es, auf Basis der Einstellungen die in der "DownGradeInfo" stehen alle Datensätze zu ermitteln die es betrifft. Das funktioniert und passiert wie gewollt am Anfag der Ausführung. Habe ich ein DataTable mit Daten erhalten, dann möchte ich dieses Zeilenweise durchgehen und einen neuen Datensatz mit der neuen Bewertung schreiben. Auch das passiert wie von mir gewollt.

Einziges wo ich jetzt prinzipiell gerade ein Problem erkannt habe ist, dass ich bei Fehlschlagen der Datenermittlung die Abarbeitung beenden sollte und nicht wie aktuell am Ende noch einmal das Event auszulösen.

Das erklärt mir alles aber immer noch nicht, warum nach Auslösen des Events - was ja ganz klar am Ende der Abarbeitungskette passiert - die Tabelle in der Datenbank noch gefüllt wird.

EDIT2:
Ok, ich sehe dass ich vermutlich den Threadpool tatsächlich bisher nicht korrekt verwendet habe. Mit entsprechenden Breakpoints habe ich nun nämlich gesehen, dass das Event ausgelöst wird - und erst danach das erste mal in die Methode "DownGradeArticle" gesprungen wird. Das Erklärt damit ja schon erst einmal das absurde verhalten.

Wissen ist nicht alles. Man muss es auch anwenden können.

PS Fritz!Box API - TR-064 Schnittstelle | PS EventLogManager |

T
2.224 Beiträge seit 2008
vor 6 Jahren

@inflames2k
Selbst in .Net 2.0, was ich im übrigen auch noch beruflich nutze, kannst du mit einem Delegate das gleiche erreichen.
Diese bieten dir dann die entsprechende Begin/End Methoden um deinen Code asynchron ausführen zu lassen.
Auch dann übernimmt der ThreadPool, aber du hast dann einfacher per End die Kontrolle über die Abarbeitung.

Aktuell wartest du auch scheinbar nicht auf die Abarbeitung, da du keinen Handle auf dein WaitCallBack.
Hier schiebst du nur dein WaitCallBack in den ThreadPool, der nach und nach deine Daten verarbeitet.
Das Verhalten, wie du es beschreibst, ist dabei das korrekte Verhalten.
Da du nich über einen Handle auf die Abarbeitung wartest, wird alles eingereiht und dein Hauptthread übernimmt wieder.
Somit ist dein Programm zwar mit einreihen fertig aber die eigentlichen Threads arbeiten dann im Hintergrund noch.

Nachtrag:
Zu deinen Edit 2:
Liegt wie gesagt daran, dass du nicht auf die Abarbeitung wartest.
Leg dir ein Delegate an, starte es mit Begin und merke dir alle aktiven Delegates.
Am ende musst du dann nur noch deine Liste durchlaufen und die End Methode aufrufen.
Dann wird auf die Abarbeitung der Threads gewartet.
Dann ist dein Programm erst fertig, wenn alles Delegates erledigt sind.

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.

inflames2k Themenstarter:in
2.298 Beiträge seit 2010
vor 6 Jahren

Somit ist dein Programm zwar mit einreihen fertig aber die eigentlichen Threads arbeiten dann im Hintergrund noch.

T-Virus

Das habe ich jetzt soweit verstanden. - Allerdings erkenne ich ja auf Basis eines Events ob die Abarbeitung fertig ist. Dieses wird in der Methode "DownGradeFinished" ausgeführt. Wie ich oben ergänzt habe, habe ich ja nun auch erkannt das aus irgend einem Grund das Event ausgelöst wird bevor überhaupt die Schleife betreten wurde. Die Stelle habe ich nun auch gefnden (warum auch immer ich geistig umnachtet die Zeile geschrieben habe).

An der Stelle wo ich ExecuteDownGrade aufrufe, wird das Event auch ausgelöst. - Also habe ich wohl den Pool doch korrekt verwendet wie erwartet und einfach nur das Event einmal zu viel ausgelöst.

Insofern kann nun das ganze Thema als gelöst und Entwicklerfehler gewertet werden.

EDIT:

Liegt wie gesagt daran, dass du nicht auf die Abarbeitung wartest.
Leg dir ein Delegate an, starte es mit Begin und merke dir alle aktiven Delegates.
Am ende musst du dann nur noch deine Liste durchlaufen und die End Methode aufrufen.
Dann wird auf die Abarbeitung der Threads gewartet.
Dann ist dein Programm erst fertig, wenn alles Delegates erledigt sind.

Nö, wie gerade ergänzt lag es an einer anderen Stelle. - Die Methode "ExecuteDownGrade" wird übrigens nie parallel mehrfach aufgerufen. - Hintergrund ist einfach das die Anwendung sich nicht aufhängen soll. Mehrere Starts sind nicht vorgesehen und die entsprechende Schaltfläche auch inaktiv bis das Event ausgelöst wird.

Wissen ist nicht alles. Man muss es auch anwenden können.

PS Fritz!Box API - TR-064 Schnittstelle | PS EventLogManager |

6.911 Beiträge seit 2009
vor 6 Jahren

Hallo inflames2k,

gut dass du die Lösung gefunden hast 😉
Fürs Debuggen ist es hier oft hilfreich einen Breakpoint in der Methode zu setzen, welchen das Event feuert (die OnXYZ-Methoden).

Da das Downgrade wohl "lange" (mehr als 1 Sekunde) dauern wird, würde ich einen extra Thread verwenden, anstatt einem aus dem ThreadPool. Und diesen Thread zur Sicherheit noch als Vordergrundthread einstellen, damit die AppDomain erst entladen wird wenn auch dieser Thread fertig ist.

Aber das hat mit dem eigentlichen Thema eh schon weniger zu tun.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"