Laden...

[gelöst] Datenabruf aus DB soll in Thread ausgelagert werden

Erstellt von Riv3r vor 16 Jahren Letzter Beitrag vor 16 Jahren 2.654 Views
R
Riv3r Themenstarter:in
206 Beiträge seit 2007
vor 16 Jahren
[gelöst] Datenabruf aus DB soll in Thread ausgelagert werden

Guten Morgen,

in einem Programm werden teilweise sehr lange Methoden aufgerufen in denen viele Selects auf eine Datenbank abgesetzt werden.

Da der Anwender sehen soll, dass der Rechner noch arbeitet habe ich mir eine Control erstellt (Animiertes GIF ruckelt) dass angezeigt werden soll sobald der User den Vorgang gestartet hat.

Mein Problem ist jetzt dass sich das gif nicht mehr dreht sobald die Methode aufgerufen wird (Grund - glaube ich: [FAQ] Warum blockiert mein GUI?).

Jetzt habe ich mir überlegt die DB Zugriffe in Threads zu packen ODER in das "WaitControl" über einen Thread laufen zu lassen.
Weiß nicht was besser ist. Habe aber keins von beiden hinbekommen.

Datenbank:


 internal static void CallMSSQLProcedure(string procedureName, ProcedureParameter[] paraArray)
        {
               SqlCommand cmd = new SqlCommand(procedureName, GlobalConnectMSSQL as SqlConnection);
                cmd.CommandType = CommandType.StoredProcedure;

                foreach (ProcedureParameter param in paraArray)
                {
                    cmd.Parameters.AddWithValue(param.Name, param.Value);
                }

                cmd.ExecuteNonQuery();
                
           }

Wo sollte ich jetzt da den Thread starten/erzeugen?

Das waitControl wird beim Klick auf einen Button einfach nur sichtbar geschalten:


//Buttonklick ruft ErstelleAngebot() auf.

private void ErstelleAngebot()
{
      waitControl.Visible = true;
      //Hier passieren die aufwendigen Dinge...
      waitControl.Visible = false;
}

Ich weiß nicht wie ich weiterkommen soll...

Die obigen FAQs habe ich gelesen sowie Controls von Thread aktualisieren lassen (Invoke-/TreeView-Beispiel) und [FAQ] Controls von Thread aktualisieren lassen (Control.Invoke)

Danke schonmal.

Gruß,
Max

49.485 Beiträge seit 2005
vor 16 Jahren

Hallo Riv3r,

ODER in das "WaitControl" über einen Thread laufen zu lassen.

lass unbedingt die Finger davon! Alle GUI-Elemente gehören in einen GUI-Thread. Das steht aber auch in der FAQ. 🙂

Wo sollte ich jetzt da den Thread starten/erzeugen?

An der Stelle, wo die langlaufende Aktion ausgeführt (genauer gesagt: angestoßen) werden soll.

herbivore

R
Riv3r Themenstarter:in
206 Beiträge seit 2007
vor 16 Jahren

Hallo herbivore,

danke für deine (wie immer) schnelle Antwort!

An der Stelle, wo die langlaufende Aktion ausgeführt (genauer gesagt: angestoßen) werden soll.

Wenn ich jedes Mal wenn eine Procedure aufgerufen wird einen eigenen Thread aumachen würde hätte ich das überall in meinen Programmen. Oder ist das nicht so sinnvoll das in der Datenschicht zu machen?

Wie soll ich den Thread starten?


internal static void CallMSSQLProcedure(string procedureName, ProcedureParameter[] paraArray)
        {
               SqlCommand cmd = new SqlCommand(procedureName, GlobalConnectMSSQL as SqlConnection);
                cmd.CommandType = CommandType.StoredProcedure;

                foreach (ProcedureParameter param in paraArray)
                {
                    cmd.Parameters.AddWithValue(param.Name, param.Value);
                }
                Thread t = new Thread(new ThreadStart(ExecuteNonQuery???????);
                t.Start();
                
                 //cmd.ExecuteNonQuery();
                
           }

Oder lieber in der Präsentationsschicht?


buttonklick
{
      waitControl.Visible = true;
      Thread t = new Thread(new ThreadStart(erstelleAngebot));
      waitControl.Visible = false;
}

erstelleAngebot()
{
      //do
}

Gruß,
Max

49.485 Beiträge seit 2005
vor 16 Jahren

Hallo Riv3r,

Wenn ich jedes Mal wenn eine Procedure aufgerufen wird einen eigenen Thread aumachen würde hätte ich das überall in meinen Programmen. Oder ist das nicht so sinnvoll das in der Datenschicht zu machen?

Unabhängig davon, ob die Datenbankoperationen schnell sind oder lange brauchen, also unabhängig davon, ob man Threads braucht, damit das GUI nicht blockiert, sollte Aufgrund der Trennung von GUI, Modell und Datenschicht, im GUI keine Datenbankoperationen durchgeführt werden. Das GUI sollte von SQL nichts wissen und nichts sehen. Alle datenbank(spezifischen) Dinge gehören in Datenschicht. Es sollte möglich sein, die Datenbank auszutauschen (z.B. eine SQL-Datenbank durch eine Objekt(daten)bank zu ersetzen), ohne dass Modell und GUI etwas davon merken.

Wenn die Datenbankoperationen lange laufen (können), dann werden Threads aufgrund der Erfordernisse des GUIs nötig. Deshalb gehört das Threading in das GUI und nicht in die Datenschicht.

Wenn du häufig langlaufende Operationen startest, dann kannst du auch einen Thread dafür verwenden, statt jedes mal einen neuen zu starten. Der Thread könnte z.B. seine Arbeitsaufträge über eine (synchronisierte) Queue bekommen.

Trotzdem ändert sich nichts daran, dass die langlaufende Aktion an der Stelle bzw. an den Stellen angestoßen werden muss, wo dies nötig ist, also i.d.R. als Reaktion auf Benutzeraktionen.

herbivore

R
Riv3r Themenstarter:in
206 Beiträge seit 2007
vor 16 Jahren

Halle herbivore,

sollte Aufgrund der Trennung von GUI, Modell und Datenschicht, im GUI keine Datenbankoperationen durchgeführt werden.

Die Trennung besteht und im GUI werden auch keine SQLs usw. ausgeführt sondern nur Methoden aus der Modellschicht aufgerufen die wiederum auf die Datenschicht Zugriff haben.

Deshalb gehört das Threading in das GUI und nicht in die Datenschicht

Threads ins GUI, alles klar!

Hab das jetzt auch versucht:


buttonklick
{
     waitControl.Visible = true;
     Thread t = new Thread(new ThreadStart(erstelleAngebot));
     t.Start();
     waitControl.Visible = false;
}

erstelleAngebot()
{
//do
            DataTable table = new DataTable();
            DataTable tableSummen = new DataTable();

            IList<string> error = IMOS_to_FAS.CreateIMOSAngebot(_currentJobId, auftragsInfos, Convert.ToInt16(tsCheckBoxInclAbteilungspos.Checked), Convert.ToInt16(tsCheckBoxInclZwischensummen.Checked), table);
            if (error.Count > 0)
            {
                //IMOS_to_FAS.ClearAngebot(auftragsInfos.ProjektName, auftragsInfos.AuftragsName);
                TSErrorLog errorLog = new TSErrorLog("Fehlerhinweis", "Bitte beheben Sie die folgenden Fehler", error);
                errorLog.ShowDialog();
            }
            bsAngebot.DataSource = table;
            if (!tsGridAngebot.IsFormatted)
            {
                tsGridAngebot.LayoutGrid("");
            }
            
            IMOS_to_FAS.ReadAngebotsAbteilungsSummen(auftragsInfos, tableSummen);
            tsGridAbteilungen.DataSource = tableSummen;
}

jetzt läuft er aber bei der BindingSource bsAngebot auf einen Fehler (Unzulässiger threadübergreifender Vorgang...).

Gruß,
Max

49.485 Beiträge seit 2005
vor 16 Jahren

Hallo Riv3r,

du darfst aus dem Thread nicht direkt auf die Controls und auch nicht auf gebundene Daten zugreifen, siehe [FAQ] Controls von Thread aktualisieren lassen (Control.Invoke)

herbivore

R
Riv3r Themenstarter:in
206 Beiträge seit 2007
vor 16 Jahren

Habs jetzt noch n bischen versucht aber bekomms irgendwie nicht hin.

Ich habe auch deinen Artikel gelesen und weiß trotzdem nicht wie ich das machen soll.
Den Teil


bsAngebot.DataSource = table;
            if (!tsGridAngebot.IsFormatted)
            {
                tsGridAngebot.LayoutGrid("");
            }
            
            IMOS_to_FAS.ReadAngebotsAbteilungsSummen(auftragsInfos, tableSummen);
            tsGridAbteilungen.DataSource = tableSummen;

muss ich irgendwie wegbekommen oder?

Hilfe wäre nett...

Gruß,
Max

49.485 Beiträge seit 2005
vor 16 Jahren

Hallo Riv3r,

Den Teil ... muss ich irgendwie wegbekommen oder?

Wenn ReadAngebotsAbteilungsSummen keine langlaufende Aktion ist, pack den Teil in eine Methode und rufe diese per Control.Invoke auf.

Wenn ReadAngebotsAbteilungsSummen eine langlaufende Aktion ist, pack den Teil vor ReadAngebotsAbteilungsSummen in einem Methode und rufe diese per Control.Invoke auf und pack den Teil nach ReadAngebotsAbteilungsSummen in einem Methode und rufe diese per Control.Invoke auf.

herbivore

R
Riv3r Themenstarter:in
206 Beiträge seit 2007
vor 16 Jahren

Hallo herbivore,

Habe das jetzt noch ein wenig versucht und siehe da es geht 😉 - fast...


private void ErstelleAngebot()
        {
            tableAngebot = new DataTable();
            tableAbteilungsSummen = new DataTable();
            IList<string> error = IMOS_to_FAS.CreateIMOSAngebot(_currentJobId, auftragsInfos, Convert.ToInt16(tsCheckBoxInclAbteilungspos.Checked), Convert.ToInt16(tsCheckBoxInclZwischensummen.Checked), tableAngebot);
            if (error.Count > 0)
            {
                TSErrorLog errorLog = new TSErrorLog("Fehlerhinweis", "Bitte beheben Sie die folgenden Fehler", error);
                errorLog.ShowDialog();
            }
            this.Invoke(new MethodInvoker(ZeigeAngebot));
        }

buttonklick
{
         waitControl.Visible = true;
         waitControl.Refresh();
         Thread thread = new Thread(new ThreadStart(ErstelleAngebot));
         thread.Start();
         waitControl.Visible = false;
}


In ZeigeAngebot() ist der ander Code:


bsAngebot.DataSource = table;
            if (!tsGridAngebot.IsFormatted)
            {
                tsGridAngebot.LayoutGrid("");
            }
            
            IMOS_to_FAS.ReadAngebotsAbteilungsSummen(auftragsInfos, tableSummen);
            tsGridAbteilungen.DataSource = tableSummen;

So jetzt 1: Läuft der ja nachdem er den Thread gestartet hat einfach weiter, d.h. man sieht das waitControl garnicht. Kann ich das Control so lange sichtbar halten wie der Thread aktiv ist?

2: Wird das Form errorLog nicht angezeigt.

Gruß,
Max

49.485 Beiträge seit 2005
vor 16 Jahren

Hallo Riv3r,

im Thread solltest du keine GUI-Elemente verwenden, also auch nicht ShowDialog. Einfach gar nichts GUI-bezogenes außer Invoke. Alles GUI-bezogene gehört alles in den GUI-Thread. Wenn du das einhältst, sollte sowohl 1. als auch 2. funktionieren.

herbivore

R
Riv3r Themenstarter:in
206 Beiträge seit 2007
vor 16 Jahren

Halle herbivore,

Aber wo soll ich dass den dann hinpacken?
Ich brauche doch die IList mit den errors...

Hast du zu Problem 1 auch eine Lösung?

Gruß,
Max

49.485 Beiträge seit 2005
vor 16 Jahren

Hallo Riv3r,

Aber wo soll ich dass den dann hinpacken?

Alles GUI-bezogene gehört in den GUI-Thread. Alles langlaufende in den (extra) Worker-Thread. Ganz einfach also.

Ich brauche doch die IList mit den errors...

Du kannst sowohl beim Starten eines Thread (GUI-Thread ==> Worker-Thread) als auch bei Control.Invoke (Worker-Thread ==> Gui-Thread) Parameter übergeben.

Hast du zu Problem 1 auch eine Lösung?

Ich bin der Meinung, dass sich beide Probleme lösen, wenn du die Aufgaben richtig auf die Threads verteilt hast.

herbivore

R
Riv3r Themenstarter:in
206 Beiträge seit 2007
vor 16 Jahren

Hallo herbivore,

dank deines Tipps

Du kannst sowohl beim Starten eines Thread (GUI-Thread ==> Worker-Thread) als auch bei Control.Invoke (Worker-Thread ==> Gui-Thread) Parameter übergeben.

funktioniert jetzt alles so wie ich mir das vorgestellt habe 😉

Gruß,
Max

R
Riv3r Themenstarter:in
206 Beiträge seit 2007
vor 16 Jahren

Ich habe noch eine kleine Frage:

Wenn ich die Methoden dann debuge läuft der Thread (worker) irgendwie auf einen Timeout oder so...

Fehlermeldung kommt keine er geht nur aus dem Debugmodus raus und hängt sich auf.

MfG
Max

EDIT:

OK liegt nicht an irgendeinem Timeout oder so - hab ne MessageBox ausgeben wollen, da hat er sich aufgehängt. Komisch aber dass ich keinen Fehler bekommen habe.