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:
// 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?
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. ;)