Hey, ich versuche grad eine Login Methode aufzurufen und das in einem Thread, weil sich ja sonst die GUI aufhängt. Ich habe auch zudem eine eigenen Rückgabetyp geschrieben aber irgendwie läuft jetzt, seitdem ich ich einen Thread nutze immer das Ergebnis auf Success also erfolgreich hin, obwohl eigentlich eine Excpetion auftrat. Wie kann das sein? Ich kriege es leider nicht alleine hin. So sieht das ganze aus: Result ReturnResult; // private Methode der Klasse private void button_Login_Click(object sender, EventArgs e) { FormData entry = new FormData() { user = textBox_Username.Text, password = textBox_Password.Text, }; Thread LoginThread = new Thread(() => Result = Database.CheckLoginData(entry)); LoginThread.Start(); switch (ReturnResult.ErrorType) { case ErrorTypes.Succeeded: { this.Hide(); MainForm mainForm = new MainForm(); adressbuchForm.Show(); break; } case ErrorTypes.Incomplete: { MessageBox.Show("Der Benutzername oder das Passwort fehlt!", "Unvöllständige Eingabe!", MessageBoxButtons.OK, MessageBoxIcon.Asterisk); break; } } } public static Result CheckLoginData(FormData entry) { sql_command.Parameters.Clear(); if (!entry.userEmpty && !entry.passwordEmpty) { try { using (sql_connection) { using (sql_command) { sql_connection.Open(); // Hier kommt die Exception // Sql Abfrage .... object userFound = sql_command.ExecuteScalar(); if (userFound != null && userFound.ToString() == "1") { return new Result(ErrorTypes.Succeeded); } } } } catch (MySqlException mysqlex) // Hier sollte er reinlaufen { return new Result(ErrorTypes.Exception, mysqlex); } return new Result(ErrorTypes.Failure); } else { return new Result(ErrorTypes.Incomplete); } } Ja, ich rufe ja in der Main Form über den button_Login die Methode in der Datenbank Klasse auf aber jetzt kriege ich immer einen erfolgreichen Methodenaufruf, also ErrorTypes.Succeeded zurück, obwohl ja eigentlich eine Exception auftrat. Beim Debuggen lief er auch bis zu sql_connection.Open(); aber dann ist auf einmal das Ergebnis Succeeded und die Hauptform öffnet sich ..
Du wartest nicht, bis der Thread fertig ist, bevor du die Switch-Case-Ueberpruefungen machst. Daher stimmt das Result nicht. Wuerdest du das tun, ist das UI aber wieder eingefroren und du kannst dir den Thread sparen. Daher musst du aus dem Login-Thread das UI informieren, wenn der Login geprueft wurde. Das kann man ueber ein Event oder einen entsprechenden Methodenaufruf mit BeginInvoke machen. Zusaetzlich solltest du den Login-Button fuer die Dauer des Pruefens deaktivieren, damit man ihn nicht mehrmals druecken kann.
@Crazyx Nur weil das Userinterface nicht blockieren soll, musst du nicht "manuell" mit Threads hantieren (schon gar nicht, wenn du lediglich auf ein externes Ereignis wartest). Stichworte wären hier async/await bzw. Task.
@Hannes: Ich habe jetzt auch mal auf den BackgroundWorker umgestellt aber das ist ja auch letzendlich nur ein Thread oder? Jetzt habe ich auch immer noch dasselbe Problem aber wie macht man das denn mit diesem BehginInvoke oder würde man das eher per Event machen? Ich kriege das einfach nicht hin. Jetzt sieht das ganze so aus: Bei button_Login dann halt einfach nur backgroundWorker_Login.RunWorkerAsync(); und die Methode dafür: private void backgroundWorker_Login_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e) { FormData entry = new FormData() { user = textBox_Username.Text, password = textBox_Password.Text, }; Result = Database.CheckLoginData(entry); } Ich habe das auch mal mit dem await probiert aber wie gebe ich dann einen Wert zurück? Die Methode müsste dann ja Task<Result> haben und wie warte ich dann darauf, bis dieser Task beendet wird? await(Task<Result>ReturnResult = CheckLoginData(entry)); funktioniert nicht. Ich kriege auch dann, wenn ich den Rückgabetypen auf Task<Result> umstelle für jedes return einen Fehler: Eine implizite Konvertierung vom Typ "Projekt.Result" in "System.Threading.Tasks.Task<Projekt.Result>" ist nicht möglich. z.B hier return new Report(ErrorTypes.Succeeded); Wie würde man denn damit einen Wert zurückgeben? Ich komme nicht weiter ....
Es sollte unten übrigens return new Result(ErrorTypes.Succeeded); heißen. Ich habe das so hingetippt und mich dann verschrieben. (Erst hießt der Rückgabetyp anders ;) )
Ja, der BackgroundWorker ist praktisch ein Thread mit ein bisschen Zucker drum herum. Wenn der Thread fertig ist, wird das RunWorkerCompleted-Ereignis gefeuert und dessen Code läuft wieder im GUI-Thread. In DoWork kann man das Rechenergebnis auch in e.Result sichern. In RunWorkerCompleted kann man daraus wieder zugreifen, dort könnte dein Code mit dem switch-Block rein. Achtung: Windows-Forms-Controls sind in der Regel nicht thread-safe! D.h. auf Textboxen, Buttons, Labels, etc. sollte man besser nur von dem Thread aus zugreifen, in dem sie erstellt wurden (typischerweise ist das der Thread mit dem das Programm gestartet ist und der wird dann häufig "GUI-Thread" genannt) D.h. die Textboxen sollte man vor .RunWorker() in irgendwelche Variablen sichern und im Thread (bzw. in DoWork()) die Variablen nutzen statt direkt die Textboxen.
Crazyx schrieb: > @Hannes: > Ich habe jetzt auch mal auf den BackgroundWorker umgestellt aber das ist > ja auch letzendlich nur ein Thread oder? Ja, siehe bluppdidupp. Crazyx schrieb: > Jetzt habe ich auch immer noch dasselbe Problem aber wie macht man das > denn mit diesem BehginInvoke oder würde man das eher per Event machen? Mit dem Backgroundworker braucht man das nicht. Dein Handler des RunWorkerCompleted-Events wird automatisch synchron zum UI-Thread ausgeführt. Siehe wieder bluppdidupp. Crazyx schrieb: > Ich habe das auch mal mit dem await probiert aber wie gebe ich dann > einen Wert zurück? Die Methode müsste dann ja Task<Result> haben und wie > warte ich dann darauf, bis dieser Task beendet wird? Unabhängig vom Backgroundworker kann man Tasks benutzen. Mit await wartet man am Ende einer Methode auf das Ergebnis einer mit async gestarteten Aufgabe. Die Schlüsselworte (seit .NET 4.5) kapseln die Task/Continuation-Fähigikeiten (seit .NET 4). Das bringt dich aber hier nicht weiter, weil du nicht am Ende der Click-Methode warten willst. Diese soll sofort beendet werden, damit das UI ansprechbar bleibt. Ohne async und await geht es aber dennoch so wie hier:
1 | private void button_Login_Click(object sender, EventArgs e) |
2 | { |
3 | Task<Result>.Factory.StartNew(() => |
4 | { |
5 | // das hier läuft asynchron; wahrscheinlich erst, wenn button_Login_Click beendet ist |
6 | Thread.Sleep(3000); // steht stellvertretend für 3 Sekunden Login-Check |
7 | |
8 | return new Result(); // gibt ein LoginResult-Objekt zurück; da muss dein Status rein |
9 | |
10 | }).ContinueWith(task => |
11 | { |
12 | // das hier läuft wieder synchron, wenn der Task beendet wurde |
13 | // (siehe TaskScheduler.FromSynchronizationContext()) |
14 | Result result = task.Result; // in task.Result ist der Rückgabewert des Tasks |
15 | // Auswertung von result |
16 | // dein Switch-Case... |
17 | |
18 | }, CancellationToken.None, TaskContinuationOptions.None, |
19 | TaskScheduler.FromCurrentSynchronizationContext()); |
20 | |
21 | // hier wird die button_Login_Click-Methode beendet, bevor der Task fertig |
22 | // abgearbeitet wurde -> das UI bleibt ansprechbar |
23 | } |
Ist vielleicht ein bisschen viel auf einmal. Versuch es mit dem BackgroundWorker. Wie bluppdidupp schreibt, kannst du im DoWork-Handler (asynchron) in e.Result ein Ergebnis ablegen, das dir im RunWorkerCompleted-Handler (synchron) in e.Result wieder zur Verfügung steht.
Ich habe es jetzt mit dem Background Worker hingekriegt aber wie hätte man das ganze denn mit dem Event oder dem BeginInvoke gemacht? Ich bin neugierig. ;) Ich mussste aber auch nachher noch e.Result auf den ErrorTypen casten aber das ist ja auch eigentlich klar. Was ist denn mit nicht thread-safe gemeint? Meinst du, das man obwohl man "Invokt" trotzdem eine Exception auftreten kann oder wie ist das gemeint? Und was ist mit dem "die Textboxen sollte man vor .RunWorker() in irgendwelche Variablen sichern und im Thread (bzw. in DoWork()) die Variablen nutzen statt direkt die Textboxen." Meinst du sowas wie TextBox textBox = new TextBox(); oder eher sowas wie private string test = string.Empty; test = textBox.Text; also die Werte der TextBox oder die komplette TextBox kopieren? (je nachdem, was man von der TextBox braucht) @Hannes Ist echt ein wenig viel, vor allem weil ich mich noch nicht so mit Lambda angefreundet habe aber das braucht man schon häufiger, richtig?(Das ist doch Lambda?) Wann sollte man denn dann einen Task benutzen und wann einen Thread, bzw. einen BackgroundWorker? Gibt es da irgendeine Richtlinie oder sollte man übehrhaupt gar keine Threadss benutzen, sondern nur BackgroundWorker? Was sind da die Vor- und Nachteile usw? Noch einmal eine kleine Frage nebenbei: Beim Start meines Programmes werden auch ziemlich viele Daten aus der Datenbank geladen, würde es sich da eigentlich auch anbieten einen Thread zu nehmen? Als ich die Methode in das Form.Load Event gepackt habe, hat sich die UI schon ziemlich aufgehangen. Jetzt ist es in dem Form.Shown Event und seitdem geht es eigentlich. Wenn ich jetzt aber einen Thread nehmen würde, würde das ganze noch einmal schneller laufen, schätze ich mal? Seitdem ich aber auch einiges da an der Methode optimniert habe, gab es auch noch einmal einen Geschwindigkeitszuwachs. Kleine Nebenfrage noch dabei: Bietet es sich eigentlich immer an, gerade bei so Verbindungssachen einen Thread zu nehmen? Schließlich kann sich die UI ja dabei auch kurz oder je nachdem länger aufhängen, wobei das bei einer normalen Exception ja auch so ist.
Crazyx schrieb: > ...aber wie hätte > man das ganze denn mit dem Event oder dem BeginInvoke gemacht? Am Ende des Threads muss man wieder auf den UI-Thread synchronisieren, wenn man an den Controls etwas ändern möchte. Das macht man in WinForms mit Control.BeginInvoke():
1 | // hier läuft es noch asynchron |
2 | Result result = new Result(); // dein Ergebnis |
3 | |
4 | this.BeginInvoke(new Action<Result>(r => |
5 | { |
6 | // das hier läuft synchron zum UI-Thread |
7 | // hier etwas mit r machen |
8 | }), result); |
this ist hier z.B. dein Control, in dem du auch die Thread-Methode definiert hast. Man kann am Ende des Threads aber auch ein selbst implementiertes Ereignis auslösen und das Ergebnis in typisierten EventArgs übergeben. Da der Handler aber im Thread ausgeführt wird, muss auch hier das BeginInvoke verwendet werden. Crazyx schrieb: > Ich mussste aber auch nachher noch e.Result auf den ErrorTypen casten > aber das ist ja auch eigentlich klar. Ja, kann man sich aber mit dem Action-Bespiel sparen, weil das typisiert ist. Die Tasks ebenso. Crazyx schrieb: > Was ist denn mit nicht thread-safe gemeint? Meinst du, das man obwohl > man "Invokt" trotzdem eine Exception auftreten kann oder wie ist das > gemeint? Nicht thread-safe bedeutet, dass es möglich ist, dass zwei Threads zeitgleich ein Objekt manipulieren können und es so zu inkonsistenten Zuständen kommt. Daher muss man Threads synchronisieren, wenn auf gemeinsamen Objekten zugegriffen werden soll. Crazyx schrieb: > (Das ist doch Lambda?) Ja. Lambda-Expressions. Man kann dafür aber auch Methoden benutzen. Mit der Zeit geht es aber mit Lambdas schneller. Crazyx schrieb: > Wann sollte man denn dann einen Task benutzen und wann einen Thread, > bzw. einen BackgroundWorker? Der Backgroundworker dürfte mit WinForms gekommen sein, und bietet halt eine einfache Möglichkeit mit Threads umzugehen. Die Tasks wurden mit WPF eingeführt und bieten noch viel mehr. Tasks sind typisiert (Generics), es gibt Mechanismen zum Abbrechen (CancellationToken) und zur Priorisierung. Man kann sogar seinen eigenen Scheduler schreiben, der dann nach eigenen Wünschen Tasks einplant und ausführt. Crazyx schrieb: > Noch einmal eine kleine Frage nebenbei: > Beim Start meines Programmes werden auch ziemlich viele Daten aus der > Datenbank geladen, würde es sich da eigentlich auch anbieten einen > Thread zu nehmen? Das würde ich so machen. Man muss sich aber überlegen, wie man den Nutzer in der Zeit "bei Laune hält". Er soll ja auch nichts unerlaubtes machen können. D.h., bestimmte Controls sollte man deaktivieren und vlt. einen Ladebalken/Fortschrittsanzeige einblenden. Crazyx schrieb: > Bietet es sich eigentlich immer an, gerade bei so Verbindungssachen > einen Thread zu nehmen? Ja, je nach Verbindungsproblem und -einstellungen kann man auch mal länger warten müssen. Da ist es gut, wenn die Anwendung nicht festgefahren wirkt. Sonst schiesst der Nutzer sie im TaskManager ab und denkt, dass sie nicht funktioniert.
Wie ist das denn das gemeint, das man am besten dann lieber nicht die Control nimmt? Sollte man dann die Werte in Variablen speichern? Die Sache ist ja auch, das die Daten nur beim Start geladen werden sollen, also sprich ich brauche den Thread oder BackgroundWorker eigentlich nur einmal und dann frage ich mich auch eigentlich. Sollte ich jetzt lieber einen Task, BackgroundWorker einen Thread nehmen? Was sind da die Vor- und Nachteile oder gibt es dafür irgendwelche Richtlinien? Beim Start sollen dann z.B diese Daten (natürlich alle in Controls gepackt werden) Nur mal so als Beipsiel: for (int i = 0; i < dataTable.Rows.Count; i++) { label_Customers.Text = dataTable.Rows[i][0].ToString(); label_LastEdited.Text = dataTable.Rows[i][1].ToString(); label_Searches.Text = dataTable.Rows[i][2].ToString(); label_Added.Text = dataTable.Rows[i][3].ToString(); label_Edited.Text = dataTable.Rows[i][4].ToString(); // usw...... } Wie soll man das alles machen? Muss man dann für jedes Control ein BeginInvoke machen? Wie du das so sagst, sollte man eigentlich am besten Tasks nehmen oder hat auch der Thread einige Vorteile im Vergleich? Ein Task ist doch ein Thread oder?? Danke auf jeden Fall schon einmal für die Hilfe. ;)
Crazyx schrieb: > Ein Task ist doch ein > Thread oder?? Nein. Der Task ist quasi ein Arbeitspaket. Der angegebene Scheduler bestimmt, wann und in welchem Thread der Task bearbeitet wird. Man kann mit .ContinueWith() (gibt auch wieder einen Task zurück) festlegen, was als nächstes gemacht werden soll. Auch diesen Task kann man wieder fortsetzen. Das kann man beliebig oft wiederholen. Standardmässig können Tasks recht schnell gestartet werden, da der Default-Scheduler* den ThreadPool** benutzt und dort immer ein paar bereits gestartete Threads vorgehalten werden (je nach Anzahl vorhandener CPUs). Ein selbst erstellter Thread hingegen braucht immer eine gewisse Zeit (z.B. eine halbe Sekunde), bis er anläuft. Wie es beim BackgroundWorker ist, weiss ich nicht. Beim Thread kümmert man sich um die Synchronisierung selbst (BeginInvoke() bei WinForms) und bei den Tasks kann das der Scheduler für einen übernehmen (Scheduler.FromSynchronizationContext() für den UI-Thread). Für deine Anwendung spielt es keine grosse Rolle, welchen Mechanismus du nutzt. Es ist ja nur eine Aufgabe, die asynchron ausgeführt werden soll und nicht zig gleichzeitig. Probiere doch einfach die Möglichkeiten durch. Dann siehst du schon, was dir besser gefällt. Fang beim Thread an (ist am einfachsten zu durchschauen) und arbeite dich zu den Tasks durch (mit Abbrechen, Fehlerbehandlung und Schedulern). Crazyx schrieb: > Muss man dann für jedes Control ein > BeginInvoke machen? Nein. Mit BeginInvoke führst du den angegebenen Code auf dem UI-Thread aus. Daher kannst du alle Controls in einem Rutsch updaten. *) Man kann sich eigene Scheduler schreiben. Eventuell möchte man z.B. nicht, dass "unendlich" viele Threads gestartet werden. Das bringt ab einer bestimmten Anzahl keine Performance-Vorteile mehr, sondern wirkt sich eher nachteilig aus, weil der Overhead zu gross wird. So kann man neue Tasks in eine Warteschlange einreihen und nach und nach auf einer festgelegten Anzahl an Threads ausführen. Dazu können auf ein und dem selben Thread mehrere Tasks bearbeitet werden (nacheinander natürlich). **) Ach ja, den gibt es ja auch noch. Mit ThreadPool.QueueUserWorkItem() kann man abzuarbeitende Aufgaben einreihen.
Also würde bzw. könnte ein Task alleine nicht für eine Aktion genutzt werden, die nebenbei ablaufen soll, also sodass das UI z.B nicht einfriert? Deine Erklärungen haben mich aber schon sehr weitergebracht, danke auf jeden Fall schon einmal dafür. Eine Sache verstehe ich allerdings nicht. Wenn ich das ganze jetzt mal mit dem einfachen Thread machen will (Die Methode, die beim Start aufgerufen werden soll). Result ReturnResult; Thread thread = new Thread(() => Result = TestDatabase()); thread.Start(); if (ReturnResult.ErrorType == ReturnType.Succeeded) { reloadDataToolStripMenuItem.PerformClick(); reloadStatsToolStripMenuItem.PerformClick(); } Dann kreige ich die Meldung, das das Feld ErrorType möglicherweise nicht zugewiesen ist, wieso ist das so? Das ist doch eine Variable, die dort im Bereich der Methode gültig sein müsste oder, oder liegt das daran, das die Methode in einem anderen Thread ausgeführt wird. Ansonsten bräuchte ich ja jetzt wieder eine Variable, die in der ganze Klasse verfügbar ist, wie kann ich das umgehen?
So müsste es eigentlich auch gehen:
1 | private async void button_Click(object sender, EventArgs e) |
2 | {
|
3 | buttonEnabled = false; |
4 | |
5 | try
|
6 | {
|
7 | var result = await CheckLoginData(textBox_Username.Text, textBox_Password.Text); |
8 | if(result == QueryResult.Success) |
9 | {
|
10 | // ..
|
11 | }
|
12 | // ...
|
13 | }
|
14 | catch (SqlException ex) |
15 | {
|
16 | // ...
|
17 | }
|
18 | // ...
|
19 | catch (Exception ex) |
20 | {
|
21 | // ...
|
22 | }
|
23 | finally
|
24 | {
|
25 | button.Enabled = true; |
26 | }
|
27 | }
|
28 | |
29 | private async Task<QueryResult> CheckLoginData(string username, string password) |
30 | {
|
31 | if (username == null) throw new ArgumentNullException("username"); |
32 | if (password == null) throw new ArgumentNullException("password"); |
33 | |
34 | if (username == String.Empty || password == String.Empty) |
35 | {
|
36 | return QueryResult.IncompleteUserData; |
37 | }
|
38 | |
39 | string queryString = "SELECT COUNT(*) from users where Username=@username AND Password=@password"; |
40 | |
41 | using (var connection = new SqlConnection(Anwendungsname.Properties.Settings.Default.DatabaseConnectionString)) |
42 | using (var command = new SqlCommand(queryString, connection)) |
43 | {
|
44 | command.Parameters.AddWithValue("@username", username); |
45 | command.Parameters.AddWithValue("@password", password); |
46 | |
47 | await connection.OpenAsync(); |
48 | bool userFound = (int)await command.ExecuteScalarAsync() != 0; |
49 | |
50 | return userFound ? QueryResult.Success : QueryResult.Failure; |
51 | }
|
52 | }
|
53 | |
54 | private enum QueryResult { Success, Failure, IncompleteUserData }; |
Man könnte natürlich das Vorhandensein von Username und Passwort schon vor dem Aufruf der Methode prüfen, dann würde bool bzw. Task<bool> als Rückgabetyp reichen. SqlConnection ließe sich auch als Parameter übergeben (wiederverwenden). BTW: Ich würde selbst in einer kleinen Anwendung alles, was nicht mit dem Userinterface zu tun hat, in eigenen Klassen implementieren und nicht in die Form stopfen.
Crazyx schrieb: > Also würde bzw. könnte ein Task alleine nicht für eine Aktion genutzt > werden, die nebenbei ablaufen soll, also sodass das UI z.B nicht > einfriert? Doch, warum nicht? Crazyx schrieb: > Dann kreige ich die Meldung, das das Feld ErrorType möglicherweise nicht > zugewiesen ist, wieso ist das so? Das ist doch eine Variable, die dort > im Bereich der Methode gültig sein müsste oder, oder liegt das daran, > das die Methode in einem anderen Thread ausgeführt wird. > Ansonsten bräuchte ich ja jetzt wieder eine Variable, die in der ganze > Klasse verfügbar ist, wie kann ich das umgehen? Das Problem hast du auch in einem einzelnen Thread, wenn du in einer bedingten Verzweigung in einem Zweig die Variable nicht setzt. Das umgehst du, wenn du vor der Verzweigung (hier Thread-Start) die Variable initialisierst. So:
1 | Result ReturnResult = null; |
Die Abfrage des Results direkt nach dem Thread-Start macht aber keinen Sinn, weil der Thread höchstwahrscheinlich nicht nicht gelaufen ist. Du musst aus dem Thread das UI informieren, wenn das Result vorliegt. Damit wären wir wieder ganz oben hier im Forums-Thread. :/
@Hannes Du hast ja gesagt, das ein Task ist also aber eine Art "Arbeitspaket", das heißt also, das der Task nicht nur ein Thread ist sondern viel mehr oder (aber mit dem Thread inklusive) an Funktionalittät, richtig? Davor habe ich das andere irgendwie ein wenig überlesen, ist das denn so richtig? Ich habe mir das auch jetzt mal mit den Thread und den Tasks ein wenig angeschaut aber so 100%ig bin ich immer noch nicht ganz dahinter gekommen. BackgroundWorker scheinen auch sehr viel einfacher zu sein, als normale Threads und Tasks ebenfalls. ManualResetEvent synchroEvent = new ManualResetEvent(false); Thread t1 = new Thread(() => { // Hier die Methode aufrufen (CheckLoginData) synchroEvent.Set(); } ); t1.Start(); synchroEvent.WaitOne(); So würde das ganze doch dann mit dem normalen Thread funktionieren? Es gibt ja auch noch ein paar andere Möglichkeiten z.B mit einem Delegate usw. (zumindest habe ich solche Beispiele gefunden)
Noch einmal eine Ergänzung. Ich wollte das ganze ja dann jetzt mit einem Thread machen oder einem Task. Mit einem BackgroundWorker würde das eher schwierig werden. Das Problem bei dieser Methode ist aber auch, das diese sehr viele Controls bearbeitet. Hier mal ein kleiner Auszug: listView_Data.Items.Clear(); Result ReturnResult = LoadCustomers(); this.BeginInvoke(new Action<Result>(result => { // läuft synchron zum UI-Thread if (result.ErrorType == ReturnType.Succeeded) { try { for (int i = 0; i < dataTable.Rows.Count; i++) { ListViewItem dataList = new ListViewItem(dataTable.Rows[i][0].ToString()); dataList.SubItems.Add(dataTable.Rows[i][1].ToString()); dataList.SubItems.Add(dataTable.Rows[i][2].ToString()); dataList.SubItems.Add(dataTable.Rows[i][4].ToString()); dataList.SubItems.Add(dataTable.Rows[i][5].ToString()); dataList.SubItems.Add(dataTable.Rows[i][6].ToString()); dataList.SubItems.Add(dataTable.Rows[i][7].ToString()); dataList.SubItems.Add(dataTable.Rows[i][3].ToString()); listView_Data.Items.Add(dataList); } } catch (Exception ex) { PrintErrorMessage(ex); return; } if (listView_Data.Items.Count == 0) InformationMessage.Set(5000, Color.Red, "Es wurde kein Datensatz gefunden!"); else InformationMessage.Set2(Adressbuch.DefaultForeColor, "Es befinden sich " + listView_Data.Items.Count + " Datensätze in der Tabelle"); } }), ReturnResult); Das ganze fängt auch schon problematisch an. Das befindet sich in einem ToolStripMenuItem. Wenn ich also auf dieses ToolStripMenuItem klicke, dann wird das ganze ausgeführt. Ich rufe das dann beim Form.Shown Event auf, also einfach mit reloadCustomersToolStripMenuItem.PerformClick(); Das ist ja auch auch schon außerhalb des Threads also wie gehe ich da am besten vor? Sollte ich dann dafür eine eigene Methode machen? Wenn ich dann das ganze komplett mit einem MethodInvoker durchlaufen lasse dann läuft zwar alles aber dann brauche ich den Thread ja auch nicht oder? this.Invoke(new Action(() => reloadCustomersToolStripMenuItem.PerformClick())); So wird ja auch alles automatisch invokt aber das will ich ja nicht. Er sollte dann ja wenigstens wenn er die Daten holt asynchron laufen (einiges an Daten (LoadCustomers() ), was er dann ja nicht macht.? Kann ich auch nur diesen einen Befehl invoken oder muss ich dafür eine extra Methode erstellen? Das ist auch nicht alles, was da gemacht wird aber die ganzen Daten werden natürlich alle in Controls geladen, lohnt sich da ein Thead?
@Crazyx > Ich wollte das ganze ja dann jetzt mit einem Thread machen Hmm ... ein Thread "zu Fuß" wäre in diesem Fall so ziemlich das Letzte, was ich verwenden würde. Es würde das gleiche passieren wie bei einem BackgroundWorker, nur dass man Rückmeldung, Kontextwechsel etc. selbst implementieren müsste. Und das alles nur, um auf das Ende der Datenbankabfrage zu warten - ohne im Thread "zu arbeiten", also parallele Berechnungen durchzuführen o.ä. Eine kurze Übersicht: http://blog.stephencleary.com/2010/08/various-implementations-of-asynchronous.html > oder einem Task. Mit einem BackgroundWorker würde das eher schwierig werden. Wie kommst du jetzt darauf? Du startest etwas und kannst reagieren, wenn es fertig ist. Bei .NET >= 4.5 würde ich aber async/await bevorzugen. Oben steht ja schon ein Beispiel; da sieht man, wie einfach das geht. > also wie gehe ich da am besten vor? Sollte ich dann dafür eine eigene > Methode machen? Genau. Eine Methode implementieren, die die entsprechenden UI-Elemente deaktiviert, irgendeine "Abfrage läuft"-Info anzeigt und den asynchronen Datenbankzugriff starten (den Zugriff selbst besser in einer Methode einer anderen Klasse ausführen, nicht in der Form). Wenn die Abfrage beendet ist, reagierst du darauf und führst im GUI-Thread das UI-Update aus - also ListView füllen, UI-Elemente reaktivieren etc. Bei WPF würde ich Data-Binding empfehlen, aber bei Windows Forms ist das eher ... holprig. Features wie das Abbrechen des Vorgangs sind bei deinem Programm (bei lokalem DB-Server oder Server im eigenen Netzwerk) mit sinnvollem Timeout vielleicht unnötig. > Er sollte dann ja wenigstens wenn er die Daten holt asynchron laufen > (einiges an Daten (LoadCustomers() ), was er dann ja nicht macht.? Ja, nur das kann hier überhaupt asynchron ablaufen. > Das ist auch nicht alles, was da gemacht wird > lohnt sich da ein Thead? Warum nicht, ist mit async/await doch fast so einfach wie synchron (und bei anderer Vorgehensweise auch nicht viel schwieriger). Ich finde es immer blöd, wenn die GUI hängt; das kann selbst bei einer halben Sekunde spürbar sein, wenn man z.B. sofort das Fenster verschieben oder minimieren will. Bei Store-Apps wird bereits ab einer Blockierdauer von 50ms asynchrone Verarbeitung empfohlen. Das ist auch einer der Hauptgründe dafür, dass es inzwischen async/await gibt (man wollte den Programmierern nicht die ständige explizite Verwendung von Tasks/Threads für jeden Furz zumuten). > aber die ganzen Daten werden natürlich alle in Controls geladen Um wie viele Einträge geht es denn? Du solltest vor dem Hinzufügen der Items "BeginUpdate" und danach "EndUpdate" aufrufen. https://msdn.microsoft.com/de-de/library/system.windows.forms.listview.beginupdate%28v=vs.110%29.aspx Extrem viele Items in einer ListBox oder ListView sind dennoch nicht so toll. Eventuell die Daten aufteilen und "seitenweise" navigieren oder die ListView virtuell machen. http://www.codeproject.com/Articles/42229/Virtual-Mode-ListView Aber natürlich nur, wenn es wirklich notwendig ist.
@Halbfertigwarenbestandszähler Ich habe auch schon alles mögliche in andere Klassen ausgelagert aber ich habe auch der Einfachheit halber hier ein paar Sachen zusammengefügt. Irgendwie muss man ja aber auch prüfen, was dabei rausgekommen ist und die Daten dann halt irgendwie dementsprechend zurückgeben. Ja, ich habe das ganze jetzt auch mit einem Task gemacht. Der Task gefällt mir bis jetzt sehr gut und der BackgroundWorker ebenfalls. Danke auch für diese Übersicht, hat mir sehr geholfen aber ich habe mir auch noch dazu noch einiges angeguckt. Await habe ich mir jetzt auch einmal angeschaut, ist auch ziemlich interessant. Das Begin-Update und das EndUpdate habe ich auch eingefügt, danke für den Hinweis. Es handelt sich halt um so viele Daten, wie in der DB vorhanden sind. Das können wenige sein aber auch sehr viele. Extrem viele Einträge sind doch immer schlecht oder nicht? Ob man nun anstatt einer ListView ein DataGrid hat ist doch egal? Ich werde warscheinlich noch eine Seitennavigation mit einfügen aber das ist ja nicht mit dabei, also müsste man das selber machen oder ebend diesen virtuellen Modus aktivieren. Danke schon einmal für die ganze Hilfe. ;)
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.