Veröffentlicht 3. November 20177 j Hallöööchen mit 3 öchen, Ich habe ein Programm mit grafischer Oberfläche (Form) und ner Menge code der losrennt sobald das Programm gestartet wurde - unter anderem 3 SQL-Abfragen die jeweils über 100 Zeilen lang sind. An sich läuft alles durch, aber vom "Klick" auf die exe-Datei bis hin zur sichtbaren Oberfläche dauert es 20 Sekunden - das passt mir nicht und ein Anwender denkt dann natürlich das es nicht funktioniert und klickt dementsprechend noch ein paarmal auf die ausführbare Datei. Meine Frage ist nun: Gibt es eine Möglichkeit die grafische Oberfläche anzuzeigen und ohne Zwischenaktion (wie Button-Click oder so) erst den Rest des Codes rennen zu laufen wenn die GUI angezeigt wird?
3. November 20177 j Hi, ein einfacher Ansatz anhand einer neu erstellten Forms-Anwendung wäre so etwas hier: namespace WindowsFormsApplication1 { static class Program { /// <summary> /// The main entry point for the application. /// </summary> [STAThread] static void Main() { Task.WhenAll( Task.Run(() => { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new Form1()); }), Task.Run(() => { var x = 1; do { Thread.Sleep(x * 1000); Debug.WriteLine(x); x++; } while (x <= 10); })).Wait(); } } } Die Anwendung ist erst beendet, wenn beide Threads beendet sind. Können die SQL-Abfragen auch parallel gestartet werden? Ist das SQL per Hand geschrieben, oder nutzt du einen ORM-Mapper für den Zugriff auf die Datenbank?
3. November 20177 j Wie/wann wird den die SQL Anfrage ausgeführt? Normalerweise hast du ja Systemevents, die deinem Programm mitteilen wann etwas passiert. Da hätte ich einfach den Code für die Abfragen erst aufgerufen, wenn die GUI soweit ist. Der ganze Spaß ist natürlich Frameworkabhänging (WPF, .NET, ...) aber hier ein Beispiel: https://msdn.microsoft.com/en-us/library/system.windows.application.loadcompleted(v=vs.110).aspx
3. November 20177 j Welcher Part vom Code ist denn nun genau für die Verzögerung des Programmstartes verantwortlich?
3. November 20177 j Es geht ja schon damit los, dass eine 20 sekündige Datenbankabfrage nach großen konzeptionellen Problemen klingt. Poste am besten mal Codefragmente, dass man sich einen Eindruck verschaffen kann. Ansonsten klingt es vor allem nach optimierter Abfrage und asynchronem absetzen der query, bei einblendung einer Warteanimation
3. November 20177 j Autor Ohje, das sah mir bis jetzt zu hoch aus. @Chief Wiggum Der Konstruktor meiner Form1 (stinknormales .NET btw) ist das Problem, da wird die Verbindung zum Oracle-SQL hergestellt und es werden die SQL-Abfragen gemacht und je nach Ergebnis werden labels mit unterschiedlicher Hintergrundfarbe versehen. Ich weiß es zwar nicht genau, aber ich denke es werden die SQL-Abfragen sein die so lange dauern, wie gesagt die gehen über 100 Zeilen und die DB ist rießig.
3. November 20177 j Lösung vor 3 Minuten schrieb Tician: @Chief Wiggum Der Konstruktor meiner Form1 (stinknormales .NET btw) ist das Problem, da wird die Verbindung zum Oracle-SQL hergestellt und es werden die SQL-Abfragen gemacht und je nach Ergebnis werden labels mit unterschiedlicher Hintergrundfarbe versehen. Ich weiß es zwar nicht genau, aber ich denke es werden die SQL-Abfragen sein die so lange dauern, wie gesagt die gehen über 100 Zeilen und die DB ist rießig. Wenn du eine einfache Lösung haben möchtest - pack den Code der die SQL Abfragen auslöst in das Form.Shown() Event. Dann wird es erst nach Anzeige gestartet. Unabhängig davon solltest du dir über das optimieren der SQL Abfragen Gedanken machen.
3. November 20177 j Autor Die SQL-Abfragen sind nicht von unserer Firma und wir dürfen da nichts dran drehen - die Datenbank übrigens auch nicht. Form.SHow-Event hört sich gut an, danke! Bearbeitet 3. November 20177 j von Tician
3. November 20177 j Ob die Db riesig ist oder nicht ist eigentlich erst einmal egal. Wenn im SQL viele Joins, Cursor, GroubBy oder Subselects verwendet werden, sollte man sich das sql nochmal angucken. Unglücklich designte Tabellen, Indicies, Trigger oder (Materialized)Views können hier auch schon ein Grund sein.
3. November 20177 j Aber selbst bei Form.Shown hast du dann eine 20 sekunden blockierte Anwendung. ich würde dringend zu einer asynchronen Query raten. (außerdem einem exception handling, dass Fehler dort fängt, wo sie passieren, nich einen globalen Exception Handler, das ist unsauber :X)
3. November 20177 j vor 1 Minute schrieb SilentDemise: Aber selbst bei Form.Shown hast du dann eine 20 sekunden blockierte Anwendung. löst aber dieses vor einer Stunde schrieb Tician: An sich läuft alles durch, aber vom "Klick" auf die exe-Datei bis hin zur sichtbaren Oberfläche dauert es 20 Sekunden - das passt mir nicht und ein Anwender denkt dann natürlich das es nicht funktioniert und klickt dementsprechend noch ein paarmal auf die ausführbare Datei. Meine Frage ist nun: Gibt es eine Möglichkeit die grafische Oberfläche anzuzeigen und ohne Zwischenaktion (wie Button-Click oder so) erst den Rest des Codes rennen zu laufen wenn die GUI angezeigt wird? Problem. Eine asynchrone Query ist natürlich schöner und löst auch das Problem der Blockade. Bearbeitet 3. November 20177 j von Gottlike
3. November 20177 j Jo - darum würd ichs kombinieren. Also die asynchrone Query nach laden des Forms feuern, nicht währenddessen. und es sollten auch dringend using statements um die Datenelemente verwendet werden, damit sie sauber wieder freigegeben werden, das fehlt in dem sample code oben auch. Bearbeitet 3. November 20177 j von SilentDemise
3. November 20177 j Autor Hab noch nie von asynchronem blablubb gehört. Ich schau erstmal das die Funktionen fertig sind, das Ding soll auch noch Mails verschicken und heute noch fertig werden. Bis es produktiv ist schau ich mal das ich das using-Zeug auf die Datei-verarbeitung bekomme, das Event richtig hinbastel und schau mal etwas verständliches über asynchrone Abfragen zu finden.
3. November 20177 j Schau dir z.b. mal https://docs.microsoft.com/de-de/dotnet/csharp/async an. Da sind recht gute Beispiele für asynchrone Ansätze.
3. November 20177 j Man sollte niemals die GUI durch einen Thread blockieren. Um das zu bewerkstelligen, kann man Threads, Tasks oder etwas in die Richtung verwenden. Ein Blick wert sind in dieser Richtung auch Reactive Extensions (kurz, rx), damit könnte man die Datenbankabfragen in einen Service auslagern, diesen von der GUI heraus subscriben und sobald das Resultat zurück ist, weiter verarbeiten. Der Vorteil hierbei ist, dass man durch asynchrone Programmierung schnell in Race Conditions, Deadlocks oder andere Probleme der asynchronen Programmierung geraten kann. Observables lassen sich zB verketten, die Daten verändern usw. Gerade eine Verkettung wäre bei mehreren Datenbankabfragen hintereinander sinnvoll, um zum Beispiel darauf zu warten, bis alle fertig sind oder die eine Abfrage Resultate aus der anderen benötigt.
6. November 20177 j Autor So, Programm funktioniert, bis mir jemand sagt das es sofort gebraucht wird habe ich also Zeit das ganze zu verbessern. Ich habe mir mal die Seite von SilentDemise angesehen und wenn ich das richtig sehe funktioniert das nur mit Events. Jetzt wurde natürlich vorher gesagt ich soll die SQL-Abfragen in das Form.Shown()-Event packen. Allerdings habe ich die Microsoft-Seite schon immer gehasst, die hat nichts was ich irgendwie verstehe. Ich weiß das meine Form1 automatisch in den Designer und meinen eigentlichen Code aufgeteilt ist. Mache ich einen doppelklick auf das Event von der Form1-GUI (so hatte ich es gelernt) wird mein Event im Designer erstellt und im Code-Teil kann ich sagen was passieren soll. Wie funktioniert das mit dem async-Ding? Ich habe etwas rumptobiert (weiß auch nicht was "=>" heißen soll, habe ich noch nie benutzt) Ich hätte es ja irgendwie so gemacht: this.Shown += async (o, e) =>{} Aber da schmeißt mich VS mit genau 68 Fehlermeldungen zu. Bearbeitet 6. November 20177 j von Tician
6. November 20177 j vor 19 Minuten schrieb Tician: Aber da schmeißt mich VS mit genau 68 Fehlermeldungen zu. Was sind das denn für Fehlermeldungen? Und: Hast du das await mit eingebaut?
6. November 20177 j Hi, ich würde die Abfragen auch nicht direkt in die Events packen. Schau, dass du die Abfragen etwas kapselst und den Aufruf dieser in die Events legst. Die async-Methoden müssen nicht events sein. Im Artikel von Microsoft, den SilentDemise gepostet hat sind auch entsprechende Beispiele hinterlegt (z.B.: async Task<User[]> GetUsers(IEnumerable<int> userIds) ). Welche Fehlermeldungen erscheinen denn genau? Folgendes async Beispiel funktioniert z.B. this.Shown += async (s,e)=> { await Task.Run(()=>Debug.WriteLine("Test")); }; Dann musst du die einzelnen Fehlermeldungen mal auseinander nehmen. Wahrscheinlich versuchst du Variablen über Threadgrenzen direkt zu setzen.Einfach blind alles async machen klappt leider nicht. Zum Thema: this.shown += async (o, e) =>{ }; ist gleichzusetzen mit ... this.shown += Form1_Shown; } private async void Form1_Shown(object sender, EventArgs e) { } Das sind sogenannte Lambda-Ausdrücke (https://docs.microsoft.com/de-de/dotnet/csharp/programming-guide/statements-expressions-operators/lambda-expressions) OffTopic: Bei den ganzen Windows-Forms gefrickel merke ich, dass ich mich in WPF mit einem MVVM-Pattern viel wohler fühle. :-)
6. November 20177 j Du kannst eine ganz normale Methode schreiben, die du durch das Event auslösen lässt: form.Shown += LoadSql; public void LoadSql(object sender, EventArgs e) { //... } Ich weiß gerade nicht genau, welche EventArgs du bei Shown nutzen musst, aber das sagt VS dir schon. Um diesen EventHandler nun asynchron auszuführen*, reicht es, die Signatur anzupassen: public async void LoadSql(...) Jetzt kannst du innerhalb der Methode await nutzen. Ich weiß jetzt nicht genau, was du machst, aber die Methode könnte (konzeptionell) so aussehen: public async void LoadSql(...) { var firstResult = database.ExecuteSqlAsync(...); var secondResult = database.ExecuteSqlAsync(...); var thirdResult = database.ExecuteSqlAsync(...); await Task.WhenAll(new[]{ firstResult, secondResult, thirdResult }); ProcessFirstResult(firstResult.Result); ProcessSecondResult(secondResult.Result); ProcessThirdResult(thirdResult.Result); } Ich habe jetzt mal angenommen, dass dir asynchrone Methoden zur Verfügung stehen, mit denen du auf deine Datenbank zugreifen kannst. Die erkennst du daran, dass sie häufig mit ...Async enden und als Rückgabewert einen Task<xyz> haben. In den ersten drei Zeilen startest du alle drei Anfragen "gleichzeitig" und in der vierten wartest du dann darauf, dass alle fertig sind. Das Warten an dieser Stelle hat den Vorteil, dass die Oberfläche währenddessen nicht hängt. Wenn du das Ergebnis der ersten Abfrage für die zweite (usw.) brauchst, kannst du das auch folgendermaßen machen: ... var firstResult = await database.ExecuteSqlAsync(...); //mache irgendwas mit firstResult var secondResult = await database.ExecuteSqlAsync(xyz); Hier würdest du zuerst auf das Ergebnis der ersten Abfrage warten (ohne die Oberfläche zu blockieren) und damit dann weiterarbeiten. Ich hoffe, das hilft erstmal für die nächsten Schritte. Noch ein Hinweis, falls du irgendwo beim Lesen drauf stößt: Normalerweise sollte man async void Methoden vermeiden (das hat was damit zu tun, wie Exceptions, die in diesen auftreten, behandelt werden) - das geht aber leider nicht, wenn man die als EventHandler nutzen möchte. *wichtig: Allein durch das Nutzen von async/await wird der Code nicht asynchron (und schon gar nicht multi-threaded). Das ganze hier hilft dir nur, wenn das, was du tust auch tatsächlich asynchron abgearbeitet wird (Dateien lesen und Netzwerk-Zugriffe sind hier so die klassischen Beispiele).
6. November 20177 j Autor Oh arlegermi <3 Mein Kopf war Hohl, mein Magen auch, jetzt nach dem Mittagessen kann ich wieder denken. Ich hatte versucht mein Event (bzw die Erstellung dessen) außerhalb von Methoden, also direkt in die Klasse zu packen, deswegen die tausend Fehlermeldungen. So, also mein Plan ist die Oberfläche zu haben, die soll ein kleines Element für Statusmeldungen haben zum Beispiel "Abfrage 1 wird ausgeführt", dann "Anfrage 2 wird ausgeführt", etc. Gleichzeitig soll sich nach jeder Abfrage ein label verändern. Gott sei Dank hat das Oracle-zeug das ich runtergeladen habe jeweils eine async-Methode zum ausführen dabei. Mein Konzelt wäre also jetzt so: public Form1() { //load some settings this.Shown += LoadSQL; } private async void LoadSQL(object sender, EventArgs e) { label.Text = "Abfrage 1 ist in bearbeitung"; DataTable anfrage1 = await SQL-Anfrage1.ExecuteReaderAsync(); //blabla paar Berechnungen label2.BackgroundColor = green; DataTable anfrage2 = await SQL-Anfrage2.ExecuteReaderAsync(); ... } Sorry für die scheiß Formatierung^^ Ich muss mir das nochmal durchlesen, ich habe es nur halbherzig verstanden, vielleicht auch mal etwas rumprobieren mit Breakpoints (geht das überhaupt?) um zu sehen was genau gleichzeitig abläuft und was wann wartet. Edit: DataTable enthält keine Definition für "GetAwaiter" ... Bearbeitet 6. November 20177 j von Tician
6. November 20177 j Wenn ich das richtig in Erinnerung habe, bekommst du aus ExecuteReaderAsync einen Reader zurück, den du mit DataTable.Load laden kannst. Aber Oracle ist da eh so 'ne Sache... guck dir mal diese Frage auf StackOverflow an, da macht jemand async mit der Oracle .NET Library: https://stackoverflow.com/questions/29016698/can-the-oracle-managed-driver-use-async-wait-properly/29034291#29034291
6. November 20177 j Autor Jetzt funktioniert es gar nicht mehr, es läuft und läuft und gibt mir meine GUI überhaupt nicht mehr - und der RAM wird immer voller. Oh man bin ich genervt, also erstmal debuggen mit Breakpoints... Das wird immer seltsamer, mit Breakpoint zeigt es mir die Oberfläche an, aber die Elemente sind alle nur weiße Vierecke und blockiert ist sie trotzdem... Bearbeitet 6. November 20177 j von Tician
6. November 20177 j Autor Code wie ich ihn habe (ich weiß das man tausend Sachen besser machen kann, aber bitte eins nach dem anderen ._.): using Oracle.ManagedDataAccess.Client; ... OracleConnection con = new OracleConnection(); DataTable dataRollkarte = new DataTable(); int sumRollkarte = 0; public Form1() { InitializeComponent(); this.Shown += LoadSQL; //Oberfläche wird angezeigt, aber blockiert, sämtliche Elemente werden nur als weiße Rechtecke angezeigt } private async void LoadSQL(object sender, EventArgs e) { //Rollkarte label7.Text = "Processing"; sumRollkarte = 0; dataRollkarte = await GetRollkarte(); foreach (DataRow dr in dataRollkarte.Rows) { sumRollkarte += Convert.ToInt32(dr[11]); //Endlosschleife } label2.Text = Convert.ToString(sumRollkarte); label1.BackColor = Color.LimeGreen; label2.BackColor = Color.LimeGreen; label7.Text = "Fertig"; } private async Task<DataTable> GetRollkarte() { dbConnect(); OracleCommand cmd = new OracleCommand(); cmd.CommandType = CommandType.Text; cmd.CommandText = @"Viel zu lange..."; cmd.Connection = con; DataTable dt = new DataTable(); using (DbDataReader dr = await cmd.ExecuteReaderAsync()) { dt.Load(dr); } dbDisconnect(); return dt; } Ich hab die komische weiße Oberfläche und eine Schleife die endlos läuft - das hat sie vor dem async noch nicht getan. Ich musste auch meine OracleDataReader-Klasse gegen die NET-eigene DbDataReader-Klasse austauschen sonst hab ich Fehlermeldungen bekommen. Edit: Endlosschleife hat sich erledigt, es läuft alles durch, aber das async-Zeug funktioniert null^^ Bearbeitet 6. November 20177 j von Tician
Erstelle ein Konto oder melde dich an, um einen Kommentar zu schreiben.