Forum: PC-Programmierung Thread iefert falsche Ergebnis [C#]


von Crazyx (Gast)


Lesenswert?

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 ..

von Hannes (Gast)


Lesenswert?

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.

von bluppdidupp (Gast)


Lesenswert?

Hier wäre vermutlich der BackgroundWorker gut geeignet.

von Halbleiter (Gast)


Lesenswert?

@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.

von Crazyx (Gast)


Lesenswert?

@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 ....

von Crazyx (Gast)


Lesenswert?

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 ;) )

von bluppdidupp (Gast)


Lesenswert?

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.

von Hannes (Gast)


Lesenswert?

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.

von Crazyx (Gast)


Lesenswert?

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.

von Hannes (Gast)


Lesenswert?

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.

von Crazyx (Gast)


Lesenswert?

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. ;)

von Hannes (Gast)


Lesenswert?

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.

von Crazyx (Gast)


Lesenswert?

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?

von Halbfertigwarenbestandszähler (Gast)


Lesenswert?

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.

von Hannes (Gast)


Lesenswert?

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. :/

von Halbdoofer (Gast)


Lesenswert?

buttonEnabled -> button.Enabled

von Crazyx (Gast)


Lesenswert?

@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)

von Crazyx (Gast)


Lesenswert?

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?

von Michael K. (brutzel)


Lesenswert?

@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.

von Crazyx (Gast)


Lesenswert?

@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
Noch kein Account? Hier anmelden.