C# Programm anhalten, nicht beenden in WinForms?
Hey, habe einen Lotto Simulator, der mir ausrechnet wie viele Versuche es braucht um 6 richtige aus 49 zu haben.
Jetzt dauert das ganze ja manchmal eeeewig :D habe einen Beenden Button hingemacht mit dem ich das Programm eben anhalte möchte und es mir dann alles ausgibt, was er bis dahin errechnet hat.
Ich finde aber nur Befehle wie this.Close(); wo das gesamte Programm schließt? Das will ich ja nicht.
Gibt es hier irgendeinen Befehl/Methode dazu? Oder habt ihr Ideen? Hier mal ein Teil von meinem Code:
while (!(numbers.Contains(value1) && numbers.Contains(value2) && numbers.Contains(value3) && numbers.Contains(value4) &&
numbers.Contains(value5) && numbers.Contains(value6)))
{
number1 = rnd.Next(minValue, maxValue);
number2 = rnd.Next(minValue, maxValue);
number3 = rnd.Next(minValue, maxValue);
number4 = rnd.Next(minValue, maxValue);
number5 = rnd.Next(minValue, maxValue);
number6 = rnd.Next(minValue, maxValue);
numbers[0] = number1;
numbers[1] = number2;
numbers[2] = number3;
numbers[3] = number4;
numbers[4] = number5;
numbers[5] = number6;
counter++;
txtResult.Text = counter.ToString();
txtMoney.Text = (money * counter).ToString();
Application.DoEvents();
}
txtResult.Text = counter.ToString();
txtMoney.Text = (counter * money).ToString();
txtWait.Text = "Fertig!";
Ist nur ein kleiner Teil der eben die Berechnung und Erstellung neuer Zufallszahlen generiert.
LG
2 Antworten
Dafür gibt's mathematische Formeln, such mal nach Stochastik :P
Aber nein, so eine Methode gibt's nicht, wird's auch nie geben. Das einzige was es gibt ist der Debugger, der kann die ganze Anwendung anhalten, dann funktioniert aber auch weder Ausgabe noch irgendein Button.
Du brauchst also einen Weg, wie Du die schleife pausieren kannst. Und damit das Programm danach nicht auch blockiert, muss die Schleife in einem eigenen Thread laufen.
Schau mal danach:
- Task-based Asynchronous Pattern - das ist der aktuell beste Weg, mit Threads zu arbeiten
- CancellationToken - Zum Abbrechen eines Tasks/Threads
- ManualResetEvent - damit kannst Du aus mehreren Threads einen anderen Thread blockieren und wieder frei geben
- lock - verhindert dass ein Stück Code von mehreren Threads gleichzeitig durchlaufen wird. Alternativ auch die Monitor-Klasse
- Deadlock - der WorstCase-Fall eines Fehlers, der auftritt, wenn durch locks mehrere Thread endlos auf einander warten
Beachte aber auch, dass Zugriffe auf gemeinsam genutzte Daten aus mehreren Threads Probleme verursachen können. Die Daten-Quelle muss also multithreading-fähig sein oder Du verhinderst, dass der Zugriff auf die Quellen durch mehrere Threads geschehen kann.
Die oben genannten Klassen sind aber alle multithreading-fähig, immerhin sollen sie dafür sorgen, eigene Klassen dafür vorzubereiten.
oh vielen Dank für deine ausführliche Erklärung! :) Aber was ich dann immer noch nicht ganz verstehe, wenn ich jetzt also das Wait(), Set(); und Reset(); gemacht habe in der schleife, wie verbinde ich das ganze dann dahingehend das es mit dem Abbrechen Button "verbunden" ist.
Weil ich will ja da drauf klicken und dann soll er das machen.
Die Methoden Set() und Reset() liegen darin.
Beachte aber auch: Es kann sein, dass Du nach dem Set() noch nicht auf die Daten aus dem Thread zugreifen darfst, weil der noch nicht bei dem Wait() angekommen ist und wartet.
Das kannst Du lösen, wenn Du das Schreiben der Daten doch mit einem lock synchronisierst. Somit zwingst Du den UI-Thread dazu, auf den Berechnungs-Thread zu warten, wenn der noch nicht pausiert ist.
Das hab ich eben übersehen :/
Ich gebe zu, das ist nicht immer ganz einfach. Ich hab jetzt im Moment drei weitere Ideen, wie man das völlig anders lösen kann, alle mit ihren ganz eigenen Vor- und Nachteilen.
Da einen Überblick zu finden, dauert Tage, Wochen bis Monate. Besonders wenn man wie ich versucht, zu verstehen, wie die Tasks im Detail funktionieren.
Im Prinzip also:
Task StartCalculation()
{
return Task.Run(() =>
{
while (xyz)
{
_myResetEvent.Wait();
// Berechnung
lock (_mySyncRoot)
_ergebnis = abc;
}
});
}
void PauseOrResumeCalculation()
{
if (_isPaused)
{
_myResetEvent.Set();
_isPaused = false;
}
else
{
_myResetEvent.Reset();
_isPaused = true;
lock (_mySyncRoot)
textBox.Text = _ergebnis.ToString();
}
}
Der Tasks ist dafür da, dass die Berechnung async läuft.
Die erste Methode startet die Berechnung.
Die zweite Methode wird durch den Button aufgerufen und kann die Berechnung pausieren oder weiter laufen lassen.
Wenn ich nix übersehen habe, sollte das tun und threadsafe sein ...
Ich würde erst mal einen BackgroundWorker probieren - der übernimmt die schwierigsten Dinge beim Taskwechsel schon mal
Der BackgroundWorker ist seit langem abgelöst. Stattdessen gibt's Tasks, die das ganze in sehr sehr sehr viel einfacher machen. Den gibt's nur noch, damit alte Projekte weiterhin bauen.
Die Tasks haben die gleichen Vorzüge, mit dem Unterschied, dass der geschriebene Code fast genau so aussieht, wie wenn man sich gar nicht um MultiThreading kümmert.
Cool, danke! Werd ich mir gleich mal alles anschauen.
Was ich noch des öfteren gelesen habe, war mit einer globalen bool Variable?
Globale Variablen gibt's in C# nicht und das ist auch gut so. Du kannst eine Variable in irgendeiner Klasse public static machen und dann von überall nutzen, aber damit baust Du dir nur Probleme.
Das WaitHandle (davon leitet ManualResetEvent ab) ist genau dafür da. Das Ding hat eine Wait-Methode, die so lange wartet, bis es wieder frei gegeben wird. Das ManualResetEvent ist eine Implementierung, die das interne Flag setzen und wieder frei geben kann. Damit kannst Du aus einem anderen Thread heraus steuern: "Warten!" oder "Lauf weiter"
Ach so, okay, dachte nur weil viele Beispiele eben mit true und false gemacht wurden und dann bei dem Button beenden eben auf false gesetzt wird.
Danke dir! Ich probier es mal damit :) Kommt dass dann alles in meine while-Schleife mit rein?
In der While-Schleife kommt einfach ein .Wait() rein.
Da, wo blockiert werden soll, musst Du .Set() aufrufen. Wenn wieder frei gegeben werden soll muss da ein .Reset() stehen.
Aber wie gesagt: Die Berechnung muss asynchron laufen, sonst blockierst Du dir den GUI-Thread und hast danach keine Möglichkeit mehr, den wieder aufzuwecken.
Und dass viele einfach nur eine bool-Variable nutzen, hängt damit zusammen, dass viele keine Ahnung von den Möglichkeiten bei Multithreading haben :D Das ManualResetEvent ist ja nur ein Weg das zu managen, es gibt noch einige andere Möglichkeiten.
Viel anders macht's das ManualResetEvent aber auch nicht, mit dem Unterschied, dass Microsoft extremen Aufwand darauf geworfen hat, dass das über x-beliebige Threads hindurch sicher läuft.
Es geht sogar so weit, dass diese Klasse nicht in C# vor liegt, die ruft nur Methoden von der kernel32.dll auf, immerhin braucht Windows solche Funktionalität selber auch.
Das müsstest Du sonst selber bauen.
Du kannst dir ja mal da eine nicht-kernel32-Variante anschauen:
https://referencesource.microsoft.com/#q=ManualResetEventSlim
Das nutzt am Ende aber auch nur die Monitor-Klasse und die reicht die Aufrufe am Ende 1:1 an die .NET-Runtime weiter.
Und in der Runtime hat Microsoft wieder extreme Manpower darauf geworfen, dass jeder Aufruf atomar und sicher ist.
Wir reden hier schließlich von Klassen, die jedes einzelne Framework in x-facher Ausführung nutzt, sobald irgendwas mit Threads zu tun hat. Immerhin sind Fehler, die mit Multithreading zu tun haben, die ekligsten Fehler :D
Wenn Du es aber - zum Ausprobieren - doch selber machen willst, dann brauchst Du die Schlüsselwörter lock oder volatile.
Das lock zwingt die Threads dazu, zu warten, wenn ein Thread da gerade arbeitet. Das ist sozusagen das grundlegendste und einfachste Prinzip, die Zugriffe von mehreren Threads zu synchronisieren. Das lock wird vom Compiler in entsprechende Aufrufe gegen die Monitor-Klasse übersetzt.
Das volatile ist dagegen völlig anders. Das unterbindet die Compiler-Optimierung. Normal kann es sein, dass ein Thread die Änderung an einer Variable nicht sofort sehen kann - durch die besagte Optimierung. Wenn nun mehrere Threads fleißig herum schreiben, kann das zu Problemen führen.
Das volatile löst aber nicht das eigentliche Problem. Wenn Du z.B. prüfst ob Du einen Wert setzen darfst und ihn danach setzt, kann es sein, dass ein anderer Thread zwischen dem Prüfen und dem Setzen selber geschrieben hat. Das Ergebnis kann ein fehlerhafter Zustand sein.
Und ich kann dir aus eigener Erfahrung sagen: So einen Fehler zu finden ist die Hölle :D
Da ich das gerade lese: Natürlich gibt es in C# auch globale Variablen. Hierzu muss erklärt werden, dass die Begriffe global/lokal von einer Perspektive aus gemacht werden.
Bsp.:
public void doSomething()
{
int a = 1;
{
int b = a + 2;
}
}
Die Variable a ist aus dem Kontext (Code-Block) heraus, in dem sich b befindet, eine globale Variable. Andersherum ist b nur lokal in ihrem Kontext bekannt (von außen betrachtet), aber generell global (in diesem betrachtet). Beide Variablen gelten innerhalb der Klasse als lokale Variablen, da sie nur in der Methode existieren.
Den größten Zugriffsbereich, den eine Variable auf sich bieten kann, ist ein statisch öffentliches Feld in einer sichtbaren Klasse (außerhalb von Klassenblöcken dürfen keine Variablen angelegt werden).
public class SomeClass
{
public static int SomeField;
}
Solche Methoden sollten aber gemieden werden, in den meisten Fällen ist es bad practice.
Du rechnest nicht sondern probierst. Du hantierst hier mit nem PRNG statt nem CSPRNG. Du bekommst Zufallsdaten mit minderwertiger qualität.
Warum berechnest du nicht einfach die warscheinlichkeit?
Zum problem: mach dir doch nen bool den du auf knopfdruck verändern kannst und hänge die bedingung das der bool auf true steht in die while schleife. Selbe prinzip geht natürlich auch mit false, suchs dir raus.
na dann :-) wenn du trotzdem noch an der variante die ich bevorzugt hätte interesse hast zu edukativen zwecken mache ich dir heute abend ein beispiel.
ich glaube das wird doch eher gegen morgen. ich bin hundemüde von der arbeit. noch ne stunde arbeit und dann gehe ich erstmal schlafen. falls ich es vergesse erinner mich daran!
Haha, kein Stress! War gestern auch todmüde..
ich hab auf ner vm windows installiert und visual studio code. wenn du möchtest kannst du mir ja mal das projekt schicken und ich versuche mal was zusammen zu basteln und sende es dir zurück
Hey, sorry musste am Freitag weg. Aber hab es jetzt geschafft anch langem probieren :D, mit einer bool Variable die ich vor der Schleife false; setze und in der Schleife mache ich dann ein break; und bei dem Abbrechen Button auf true; setzen.
Vielen Dank für deine Hilfe!
ein break in der while schleife (falls du dich für die variante mit if entschieden hast) stoppt die while schleife. ich kann dir gerne ein beispiel schreiben zu deinem code. allerdings erst später. wäre nett wenn du sofern nichts dagegen spricht den code auf pastebin.org mit syntax higlighting für c# postest. das ist etwas angenehmer zu lesen und kopieren. und wie von @Palladin007 bereits erwähnt sollten die versuche in einem seperaten thread ausgeführt werden damit die main methode nicht blockiert und du dein stop event überhaupt setzen kannst (bool ändern). ich muss zugeben ich hab seit über 3 jahren nichtmehr mit c# gearbeitet aber ich denke das bekommen wir schon noch hin.
kein problem. verzeihe mir falls ich manchmal unfreundlich wirke. das ist nicht böse gemeint :-). das mit dem abbrechen via button->bool verändern ist eine möglichkeit das problem zu lösen, ja. ist es für dich verständlich wie ich das meine?
Alles gut :-)
Ja, das hab ich verstanden, nur die Umsetzung hapert immer etwas :D Aber nicht dass du mich falsch verstehst, ich will des Programm ja anhalten und nicht komplett beenden. Weil mit einem break; würde er es mir ja beenden.
Und wenn ich dass dann in die while Schleife schreibe, daann macht er grad iwie gar nix bei mir :( :D
warum denn eine if? du kannst es doch zu den bedingungen in der while zeile hinzufügen..? ansonsten ja. eine if mit entsprechendem break wäre auch möglich (aber unelegant).
Okay, ja sorry bin totaler Anfänger in C# noch und is bisschen verwirrend alles am Anfang :) Aber mit dem Abbrechen Button stimmt schon oder, dass da dann der bool auf false gemacht wird?
Wenn Du Anfänger bist, ist das vielleicht nicht das beste Projekt :D
Wenn es dir um's Beenden der Schleife und damit der Berechnung geht, dann ist das, was Leonard schreibt, das Mittel der Wahl.
Wenn Du die Berechnung pausieren willst, hast Du zwei Möglichkeiten:
- Du baust die Berechnung so um, dass Du zu einem beliebigen Zeitpunkt beenden kannst und ein erneutes Starten dort weiter arbeiten würde, wo es vorher aufgehört hat
- Du pausierst die Berechnung
In beiden Fällen muss die Berechnung aber in einem eigenen Thread (nichts anderes ermöglichen dir Tasks) liegen.
Der Grund ist, dass der GUI-Thread so lange blockiert ist, bis die Berechnung abgeschlossen ist. Das heißt, einen Button kannst Du nicht drücken.
Eine Möglichkeit, das zu "lösen" ist das Application.DoEvents()
Das ist allerdings sehr sehr unschön, weil Du damit unvorhergesehene Seiteneffekte verursachen kannst.
Richtig wäre, wenn Du das nicht brauchst, weil der GUI-Thread nie blockiert wird.
Hey,
also in die Schleife eine If mit true und dann bei dem Button den bool auf false?
Eine kleine Ergänzung noch:
Wenn deine Berechnung in einem eigenen Task läuft, musst Du aufpassen, dass die Control-Properties nicht geschrieben werden.
Ich würde - während die Berechnung läuft - gar keine Anzeige machen und stattdessen z.B. eine Loading-Animation anzeigen.
Erst wenn pausiert ist, zeigst Du an.
Auf diese Weise hast Du einen gewaltigen Vorteil:
Du brauchst dich nicht um die Zugriffe aus zwei Threads kümmern.
Läuft die Berechnung, schreibt sie fleißig in deine Variablen (z.B. numbers) und kein anderer Thread fast sie an. Beim pausieren, blockierst Du den Berechnungs-Task und liest danach (wenn der Thread pausiert ist) die Werte aus um sie anzuzeigen.
Wenn Du trotzdem live anzeigen willst, brauchst Du den Dispatcher von WinForms/WPF, der kann den übergebenen Delegaten im UI-Thread ausführen. Bei WPF ists glaube nur this.Dispatcher.Invoke()
Dann hast Du aber das Problem, dass das langsam ist. Du bremst nur durch das Anzeigen deine Berechnung stark aus.
Wenn Du einen Mix haben willst, kannst Du auch nach jedem 1000ten Durchlauf kurz anzeigen. Die nötige counter-Variable hast Du ja schon.