Windows Forms Applikation reagiert kurzzeitig nicht

  • C#

Es gibt 7 Antworten in diesem Thema. Der letzte Beitrag () ist von SpaceyX.

    Windows Forms Applikation reagiert kurzzeitig nicht

    Hallo,

    ich habe mal wieder eine kleine Windows Forms Anwendung für mich geschrieben. Das Programm als solches Funktioniert auch, nur mich treibt die Frage umher, wie man verhindern kann das Windows Forms Anwendungen einfrieren.
    Ich will das einmal näher ausführen. Angenommen ich habe eine aufwendige rekursive Funktion, die aufgerufen wird sobald ein Button gedrückt wurde. Dann kommt es häufig vor, dass das Programm einfriert. Je nachdem wie aufwendig der Funktionsaufruf ist, kann dies auch mal etwas dauern bis zu einer Minute, in der das Fenster nicht ansprechbar ist! Im Task Manager erscheint dann auch "Keine Rückmeldung". Das Programm selbst fängt sich aber und nach etwa einer Minute habe ich wieder Kontrolle über das Programm.

    Das sind beispielsweise Dinge, die passieren, wenn man eine sehr große Textdatei nach einem speziellen Suchstring durchsucht oder bei Verzeichnisstrukturen.

    Meine Frage ist nun, wie kann man den generell verhindern, dass Windows Forms Applikationen einfrieren? ISt Multithreading da eine Möglichkeit? Fernen würde ich gerne diesen Prozess auch mit einer Progressbar visualisieren wollen. Das dürfte ja vermutlich bekannt sein, dass es Suchprogramme gibt, die dann eben nicht einfrieren, sondern dem Nutzer beispielsweise über eine Progressbar informieren, dass derzeit noch gesucht wird, das würde ich gerne koppeln wollen. Meine Frage ist daher, wie gestaltet man sowas?
    Da ist Multithreading denke ich mal bereits das korrekte Stichwort, um aber aus dem Thread/Task heraus auch informationen rauslesen zu können, wirst du auch einen DeleGateSub benötigen.
    Im folgenden Beispiel habe ich auf der Form1, eine Progressbar mit .max 10000, .min 0 und einen Button zum starten.

    Spoiler anzeigen

    VB.NET-Quellcode

    1. Dim Upd As New Update_UI(AddressOf Update_UISub)
    2. Delegate Sub Update_UI()
    3. Public Sub Update_UISub()
    4. ProgressBar1.PerformStep()
    5. End Sub
    6. Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    7. StartTask()
    8. End Sub
    9. Sub StartTask()
    10. Dim IntensiveTaskThread As Thread = New Thread(Sub() IntensiveTask())
    11. IntensiveTaskThread.SetApartmentState(ApartmentState.STA)
    12. IntensiveTaskThread.Start()
    13. End Sub
    14. Sub IntensiveTask()
    15. For i As Integer = 0 To 9999
    16. Thread.Sleep(500)
    17. Me.Invoke(Upd)
    18. Next
    19. End Sub
    If Energy = Low Then
    Drink(aHugeCoffee)
    Else
    Drink(aHugeCoffeeToo)
    End If
    Das geht kürzer und moderner mit Async/Await:

    VB.NET-Quellcode

    1. Private Async Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    2. Await Threading.Tasks.Task.Run(Sub()
    3. For i As Integer = 0 To 9999
    4. Threading.Thread.Sleep(500)
    5. Me.Invoke(Sub() ProgressBar1.PerformStep())
    6. Next
    7. End Sub)
    8. End Sub
    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „VaporiZed“, mal wieder aus Grammatikgründen.

    Häufig von mir verwendete Abkürzungen: CEs = control elements (Labels, Buttons, DGVs, ...) und tDS (typisiertes DataSet)
    Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht in den Spekulatiusmodus gehen.
    @VaporiZed
    Das geht kürzer und moderner mit Async/Await:


    Woooow ! das ist ja mal genial, mehr als deutlich weniger zu tippen, Danke dir !!!
    Aber eine Frage noch, mit Async/Await, wird dabei der Haupthread, so nicht etwas mehr belastet als mit einem extra thread ? oder hab ich da nur etwas falsch im Kopf (nur relevant wenn man mal eine sehr rechenintensive methode hat)
    If Energy = Low Then
    Drink(aHugeCoffee)
    Else
    Drink(aHugeCoffeeToo)
    End If
    Mit Task.Run() (neuer, abgespeckte Version von Task.Factory.StartNew()), bzw. Task.Factory.StartNew() (etwas älter) erreicht man keine wirkliche Nebenläufigkeit. Die Codeausführung stoppt beim Erreichen des Await-Schlüsselwortes und wartet asynchron auf die Beendigung dieser Methode. Solltest Du in die Versuchung kommen, hier sehr zeitintensive Berechnungen durchzuführen, dann wirst Du zwar merken, dass die Form nicht einfriert, aber bis zur Beendigung auch nicht wirklich etwas passiert, was nach dem Aufruf kommt. Hier wird ein Thread aus dem (auf dem Betriebssystem) bereits vorhandenen Threadpool genutzt.

    Async/Await würde vornehmlich für I/O-Aufgaben (Lesen von Dateien, Netzwerkkommunikation usw.) gemacht. Nicht unbedingt für rechenintensive Aufgaben. Behalte das im Hinterkopf. Für intensive Berechnungen würde ich weiter auf die altbekannten Threads setzen, wenn sich der Aufwand für das Erstellen eines solchen rentiert. Threads erzeugen kostet Zeit, Threads aus dem Pool sind bereits da.

    Hier ein paar Beispiele... (C#)

    C#-Quellcode

    1. private async void button1_Click(object sender, EventArgs e)
    2. {
    3. await Task.Run(() =>
    4. {
    5. double result = 0;
    6. for (int i = 0; i < 60000000; i++)
    7. {
    8. result += Math.Pow(120, i);
    9. }
    10. MessageBox.Show(result.ToString());
    11. });
    12. MessageBox.Show("hello");
    13. }


    Zuerst kommt die MessageBox mit dem Ergebnis aus dem Task, dann die Messagebox hello. Die Codeausführung wartet solange bis Await fertig ist.

    C#-Quellcode

    1. private async void button1_Click(object sender, EventArgs e)
    2. {
    3. new Thread(() =>
    4. {
    5. double result = 0;
    6. for (int i = 0; i < 60000000; i++)
    7. {
    8. result += Math.Pow(120, i);
    9. }
    10. this.textBox1.Invoke(new Action(() =>
    11. {
    12. MessageBox.Show(result.ToString());
    13. }));
    14. }).Start();
    15. MessageBox.Show("hello");
    16. }


    Hier kommt sofort die MessageBox hello und die Ausgabe des Ergebnisses später. Wirkliche Nebenläufigkeit.

    C#-Quellcode

    1. private async void button1_Click(object sender, EventArgs e)
    2. {
    3. new Action(() =>
    4. {
    5. double result = 0;
    6. for (int i = 0; i < 60000000; i++)
    7. {
    8. result += Math.Pow(120, i);
    9. }
    10. MessageBox.Show(result.ToString());
    11. }).BeginInvoke(null, null);
    12. MessageBox.Show("hello");
    13. }


    Selbiges gilt für den Aufruf von BeginInvoke... Soweit ich weiß, wird hier auch ein PoolThread verwendet, nagel mich aber darauf nicht fest. Auch hier wird Nebenläufigkeit erreicht. Die MessageBox hello kommt sofort, das Ergebnis später.

    Außerdem solltest Du darauf verzichten, innerhalb von Threads oder Tasks ständig auf Controls zuzugreifen. Mache das nur ab und an, weil auch dies kostet enorm viel Zeit und kann die Anwendung zum stocken bringen. Ein Update einer ProgressBar muss nicht bei jedem Schleifendurchgang erfolgen.

    Async/Await ist ein wunderbares Werkzeug, wenn man es einzusetzen weiß. Wie gesagt vornehmlich wurde es für I/O-Aufgaben geschaffen.
    Die Unendlichkeit ist weit. Vor allem gegen Ende. ?(
    Manche Menschen sind gar nicht dumm. Sie haben nur Pech beim Denken. 8o

    How to turn OPTION STRICT ON
    Why OPTION STRICT ON
    Ich fasse gedanklich Async-Await-Prozeduren für mich so zusammen:
    • arbeite Teil 1 ab (bei Post#3: zwischen Zeile#1 und #2 - im dortigen Code also nix)
    • erwarte (await) das Ende von folgender Aufgabe (Z#2 bis #7)
    • mach danach weiter mit Teil 2 (zwischen Z#7 und #8 - auch nicht vorhanden)
    der Arbeitsablauf »echter Nebenläufigkeit«, so wie Du es nennst, kann man aber auch erreichen:

    VB.NET-Quellcode

    1. Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    2. DoTheFirstPart()
    3. DoTheHeavyJob()
    4. DoTheSecondPart()
    5. End Sub
    6. Private Sub DoTheFirstPart
    7. MessageBox.Show("Part 1")
    8. End Sub
    9. Private Async Sub DoTheHeavyJob()
    10. Await Threading.Tasks.Task.Run(Sub()
    11. For i As Integer = 0 To 9999
    12. Threading.Thread.Sleep(500)
    13. Me.Invoke(Sub() ProgressBar1.PerformStep())
    14. Next
    15. End Sub)
    16. MessageBox.Show("endlich fertig")
    17. End Sub
    18. Private Sub DoTheSecondPart
    19. MessageBox.Show("Part 2")
    20. End Sub

    Nun taucht nach der 1. MessageBox sofort die 2. auf - obwohl die nebenläufige Aufgabe noch am werkeln ist.

    (Dies ist keine Gegendarstellung zu Deinem Post, sondern eine Ergänzung)
    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „VaporiZed“, mal wieder aus Grammatikgründen.

    Häufig von mir verwendete Abkürzungen: CEs = control elements (Labels, Buttons, DGVs, ...) und tDS (typisiertes DataSet)
    Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht in den Spekulatiusmodus gehen.
    @VaporiZed hey!!!

    Du hast natürlich recht mit Deinem Beispiel. Nur leider läuft man bei diesem Muster schnell in die Gefahr, den UI-Thread ins Deadlock zu schicken. Die passiert, wenn man beispielsweise in einem Task eine Berechnung ausführt und auf das Ergebnis wartet.

    VB.NET-Quellcode

    1. Imports System.Threading
    2. Public Class Form1
    3. Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    4. MessageBox.Show(Pow(10, 10).ToString())
    5. MessageBox.Show(PowTaskAsync(10, 10).Result.ToString())
    6. End Sub
    7. Private Function Pow(a As Integer, b As Integer) As Double
    8. Return Math.Pow(a, b)
    9. End Function
    10. Private Async Function PowTaskAsync(a As Integer, b As Integer) As Task(Of Double)
    11. Return Await Task.Run(Function()
    12. Return Math.Pow(a, b)
    13. End Function)
    14. End Function
    15. End Class


    Dem Task-Object wird ein "Continuation"-Context mitgegeben, was in diesem Fall der UI-Thread ist, denke ich. Der UI-Thread wartet nun auf das Ergebnis, dies wird aber nicht eintreffen können, da der Task ebenfalls auf den UI-Thread wartet, bis dieser frei ist -> Deadlock.

    Die Lösung hierfür ist folgende...

    VB.NET-Quellcode

    1. IImports System.Threading
    2. Public Class Form1
    3. Private Async Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    4. MessageBox.Show(Pow(10, 10).ToString())
    5. MessageBox.Show((Await PowTaskAsync(10, 10)).ToString())
    6. MessageBox.Show(Pow(10, 20).ToString())
    7. End Sub
    8. Private Function Pow(a As Integer, b As Integer) As Double
    9. Return Math.Pow(a, b)
    10. End Function
    11. Private Async Function PowTaskAsync(a As Integer, b As Integer) As Task(Of Double)
    12. Return Await Task.Run(Function()
    13. Return Math.Pow(a, b)
    14. End Function)
    15. End Function
    16. End Class


    Es muss mit Await "gewartet" werden, um das Deadlock zu vermeiden. Somit ist man wieder beim erwähnten Prozess, dass hier keine echte Nebenläufigkeit stattfindet. Das nur am Rande. Du hast natürlich recht und ich hätte mich etwas genauer ausdrücken sollen. Hier warten einige Fallstricke, die man beachten muss.
    Die Unendlichkeit ist weit. Vor allem gegen Ende. ?(
    Manche Menschen sind gar nicht dumm. Sie haben nur Pech beim Denken. 8o

    How to turn OPTION STRICT ON
    Why OPTION STRICT ON