Task starten und abbrechen

  • VB.NET
  • .NET (FX) 4.5–4.8

Es gibt 20 Antworten in diesem Thema. Der letzte Beitrag () ist von RodFromGermany.

    Task starten und abbrechen

    Hallo,

    ich versuche zu verstehen wie das mit den Token funktioniert und wie man einen Task abbricht. Ich hab da bissle was an nem Beispiel rumgebastelt.
    Das wirft eine OperationCanceledException (ohne den Try Block), die wird im Beispiel auch erwähnt. Und ich denke mal die wird mit dem Try Catch dann abgefangen, allerdings scheine ich da irgendwo hängenzubleiben

    Was ich eigentlich wollte war den Task starten und beenden zu können mit den Buttons. Was ist das Problem hier?

    Spoiler anzeigen

    VB.NET-Quellcode

    1. Public Class Form1
    2. Private progress As Integer
    3. Private TokenSource As New CancellationTokenSource
    4. Private token As CancellationToken = TokenSource.Token
    5. Private t1 As Task
    6. Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
    7. t1 = Task.Run(Sub() start())
    8. t1.Wait()
    9. End Sub
    10. Private Sub start()
    11. End Sub
    12. Private Sub startButton_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles startButton.Click
    13. If t1.Status = TaskStatus.RanToCompletion OrElse t1.Status = TaskStatus.Canceled Then
    14. t1 = Task.Run(Sub() bgw1(token), token)
    15. End If
    16. End Sub
    17. Private Sub cancelButton_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cancelButton.Click
    18. TokenSource.Cancel()
    19. Try
    20. t1.Wait()
    21. Catch ex As AggregateException
    22. For Each item In ex.InnerExceptions
    23. MessageBox.Show(ex.Message & " " & item.Message)
    24. Next
    25. Finally
    26. TokenSource.Dispose()
    27. End Try
    28. End Sub
    29. Private Sub bgw1(ct As CancellationToken)
    30. If ct.IsCancellationRequested Then
    31. ct.ThrowIfCancellationRequested()
    32. Invoke(Sub() Resultlabel.Text = "Did not start")
    33. End If
    34. progress = 0
    35. For i = 1 To 10
    36. System.Threading.Thread.Sleep(500)
    37. progress = i * 10
    38. Invoke(Sub() Resultlabel.Text = progress.ToString + "%")
    39. If ct.IsCancellationRequested Then
    40. ct.ThrowIfCancellationRequested()
    41. Invoke(Sub() Resultlabel.Text = "Canceled")
    42. End If
    43. Next i
    44. End Sub
    45. End Class


    Ursprüngliches Beispiel:
    Spoiler anzeigen

    VB.NET-Quellcode

    1. Module Test
    2. Sub Main()
    3. Dim tokenSource2 As New CancellationTokenSource()
    4. Dim ct As CancellationToken = tokenSource2.Token
    5. Dim t2 = Task.Factory.StartNew(Sub()
    6. ct.ThrowIfCancellationRequested()
    7. Dim moreToDo As Boolean = True
    8. While moreToDo = True
    9. If ct.IsCancellationRequested Then
    10. ct.ThrowIfCancellationRequested()
    11. End If
    12. End While
    13. End Sub
    14. tokenSource2.Cancel()
    15. Try
    16. t2.Wait()
    17. Catch e As AggregateException
    18. For Each item In e.InnerExceptions
    19. Console.WriteLine(e.Message & " " & item.Message)
    20. Next
    21. Finally
    22. tokenSource2.Dispose()
    23. End Try
    24. Console.ReadKey()
    25. End Sub
    26. End Module

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Haudruferzappeltnoch“ ()

    Haudruferzappeltnoch schrieb:

    Wieso ist es im Beispiel dann mit der Exception gemacht?
    Naja, so ists von den Erfindern des Task-Brimboriums wohl vorgesehen - deshalb.

    Ob das besser ist als was Fakiz vorschlägt weiss ich nicht - er hat ja kein Codebeispiel.

    Bei deim Codebeispiel wundere ich mich, warum du mit Task.Factory herumorgelst, anstatt einfach Async/Await zu verwenden.

    Haudruferzappeltnoch schrieb:

    Was ich eigentlich wollte war den Task starten und beenden zu können mit den Buttons.
    Dazu habichja ein englisches Tut verlinkt - was das genau vorturnt, tja, was sollich noch sagn?
    @Haudraufzappeltnoch
    Ich kenn das Beispiel nicht, würde aber vermuten daß dort beschrieben wird warum mit einer Ausnahme gearbeitet wird.
    Ich an deiner Stelle würde mir die CancelationTokenSource im Object Browser oder bei MSDN einmal genauer angucken. Also welche Methoden es gibt und welche sich davon als Exception Ersatz anbieten würden. Also eine Methode die den Task Status auf Canceled fest legt.
    @Haudruferzappeltnoch Unter welchen Bedingungen soll denn dieser Task abgebrochen werden?
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    @Haudruferzappeltnoch Hast Du Dir mal diese Beispiele angesehen: docs.microsoft.com/de-de/dotne…ken?view=netframework-4.8
    Die gute alte Methode besteht darin, dass Du von außen ein Flag setzt, das Du in der Thread-Prozedur auswertest und diese dann einfach mit Return verlässt.
    Pseudocode:

    Quellcode

    1. Dim Flag As Boolean
    2. Sub ButtonClick()
    3. Flag = True
    4. End Sub
    5. Sub Thread_Prozedur
    6. Flag = False
    7. Do ' Endlosschleife
    8. If Flag Then Return
    9. Loop
    10. End Sub

    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Ne die hatte ich noch nicht gesehen. Ich hab es jetz auch mal versucht aus dem Tutorial von EDR zu reproduzieren. Er arbeitet auch mit CancellationToken, aber bei mir gibt wieder nur eine Fehlermeldung bezüglich OperationCanceled.
    Ich weiß nicht was ich da falsch übernommen habe. Das war der Link: codeproject.com/Articles/10296…ithout-any-additional-Lin

    Spoiler anzeigen

    VB.NET-Quellcode

    1. Public Class Form1
    2. Private WithEvents progress As New IntervalProgress(Of Integer)
    3. Private Cts As CancellationTokenSource
    4. Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
    5. btCancel.Enabled = False
    6. Reportlabel.Visible = False
    7. End Sub
    8. Private Async Sub startButton_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btStart.Click
    9. btCancel.Enabled = True
    10. btStart.Enabled = False
    11. Reportlabel.Visible = True
    12. Cts = New CancellationTokenSource
    13. Try
    14. Await Task.Run(Sub() bgw1(Cts.Token))
    15. Catch ex As OperationCanceledException
    16. Reportlabel.Text = "cancelled"
    17. End Try
    18. btStart.Enabled = True
    19. btCancel.Enabled = False
    20. Reportlabel.Visible = False
    21. End Sub
    22. Private Sub cancelButton_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btCancel.Click
    23. Cts.Cancel()
    24. End Sub
    25. Private Sub progress_ProgressChanged(sender As Object, e As Integer) Handles progress.ProgressChanged
    26. Reportlabel.Text = e & "%"
    27. End Sub
    28. Private Sub bgw1(ct As CancellationToken)
    29. For i = 1 To 10
    30. ct.ThrowIfCancellationRequested()
    31. System.Threading.Thread.Sleep(500)
    32. progress.Report(i * 10)
    33. Next i
    34. End Sub
    35. End Class
    36. Public Class IntervalProgress(Of T) : Inherits Progress(Of T)
    37. Public Interval As TimeSpan = TimeSpan.FromMilliseconds(300)
    38. Private _LastReport As Date = Date.MinValue
    39. Public Sub New()
    40. End Sub
    41. Public Sub New(action As Action(Of T))
    42. MyBase.New(action)
    43. End Sub
    44. Protected Overrides Sub OnReport(value As T)
    45. Dim dt = Date.Now
    46. If dt - _LastReport < Interval Then Return
    47. _LastReport = dt
    48. MyBase.OnReport(value)
    49. End Sub
    50. Public Sub Report(arg As T)
    51. OnReport(arg)
    52. End Sub
    53. Public Sub FinalReport(arg As T)
    54. _LastReport = Date.MinValue
    55. OnReport(arg)
    56. _LastReport = Date.MinValue
    57. End Sub
    58. End Class

    Da fliegt der Fehler, weil du ihn ja auch explizit wirfst mit ct.ThrowIfCancellationRequested()



    Du musst direkt dort den Fehler fangen und behandeln.

    VB.NET-Quellcode

    1. Private Sub bgw1(ct As CancellationToken)
    2. For i = 1 To 10
    3. Try
    4. ct.ThrowIfCancellationRequested()
    5. Catch ex As Exception
    6. MessageBox.Show("Der Benutzer hat auf Abbrechen geklickt.")
    7. Exit Sub
    8. End Try
    9. System.Threading.Thread.Sleep(500)
    10. progress.Report(i * 10)
    11. Next i
    12. End Sub


    oder eben keinen Fehler werfen, sondern das CancellationToken Objekt mal genauer anschauen ob man da nicht evtl. was prüfen kann ob der Abbruch statt gefunden hat.

    Spoiler anzeigen

    VB.NET-Quellcode

    1. Private Sub bgw1(ct As CancellationToken)
    2. For i = 1 To 10
    3. If (ct.IsCancellationRequested) Then
    4. MessageBox.Show("Der Benutzer hat auf Abbrechen geklickt.")
    5. Exit Sub
    6. End If
    7. System.Threading.Thread.Sleep(500)progress.Report(i * 10)
    8. Next i
    9. End Sub

    "Gib einem Mann einen Fisch und du ernährst ihn für einen Tag. Lehre einen Mann zu fischen und du ernährst ihn für sein Leben."

    Wie debugge ich richtig? => Debuggen, Fehler finden und beseitigen
    Wie man VisualStudio nutzt? => VisualStudio richtig nutzen

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „mrMo“ ()

    Ok, das kann ich eigentlich nachvollziehen.
    Aber was ich dann nicht verstehe ist in dem Beispiel und im Tut scheint das auch außerhalb des Tasks mit Try Catch behandelt zu funktionieren.
    Wird sogar extra erklärt: "Catching its OperationCanceledException in LaunchGetData() detects, if the Process was finished by cancelation."
    LaunchGetData ist bei mir soweit ich sehen kann die Entsprechung von der btStart.Click Sub


    Also so funktioniert es, danke, aber der Catch entspricht halt nichtmehr den Quellen, das finde ich verwirrend und ich weiß auch nicht ob das so denn auf irgendeine Weise trotzdem falsch ist.

    Spoiler anzeigen

    VB.NET-Quellcode

    1. Public Class Form1
    2. Private WithEvents progress As New IntervalProgress(Of Integer)
    3. Private Cts As CancellationTokenSource
    4. Private CanceledFlag As Boolean
    5. Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
    6. CanceledFlag = False
    7. btCancel.Enabled = False
    8. Reportlabel.Visible = False
    9. End Sub
    10. Private Async Sub startButton_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btStart.Click
    11. CanceledFlag = False
    12. btCancel.Enabled = True
    13. btStart.Enabled = False
    14. Reportlabel.Visible = True
    15. Cts = New CancellationTokenSource
    16. Await Task.Run(Sub() bgw1(Cts.Token))
    17. If CanceledFlag Then
    18. Reportlabel.Text = "canceled"
    19. Else
    20. Reportlabel.Text = "fertig"
    21. End If
    22. btStart.Enabled = True
    23. btCancel.Enabled = False
    24. End Sub
    25. Private Sub cancelButton_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btCancel.Click
    26. Cts.Cancel()
    27. End Sub
    28. Private Sub progress_ProgressChanged(sender As Object, e As Integer) Handles progress.ProgressChanged
    29. Reportlabel.Text = e & "%"
    30. End Sub
    31. Private Sub bgw1(ct As CancellationToken)
    32. Try
    33. For i = 1 To 10
    34. ct.ThrowIfCancellationRequested()
    35. System.Threading.Thread.Sleep(500)
    36. progress.Report(i * 10)
    37. Next i
    38. Catch ex As OperationCanceledException
    39. CanceledFlag = True
    40. Return
    41. End Try
    42. End Sub
    43. End Class

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „Haudruferzappeltnoch“ ()

    Es geht auch so wie du es hattest

    VB.NET-Quellcode

    1. Private Async Sub berechnen_Click(sender As Object, e As EventArgs) Handles berechnen.Click
    2. Cts = New CancellationTokenSource
    3. Try
    4. Await Tasks.Task.Run(Sub() bgw1(Cts.Token))
    5. Catch ex As Exception
    6. MessageBox.Show("Abbruch durch benutzer")
    7. End Try
    8. End Sub

    Aber die IDE hält halt beim Fehler / Catch an, sofern die Ausnahmen nicht deaktiviert wurde.
    Startest du dein Programm (also die .exe) separat, so klappt auch das Beispiel.
    "Gib einem Mann einen Fisch und du ernährst ihn für einen Tag. Lehre einen Mann zu fischen und du ernährst ihn für sein Leben."

    Wie debugge ich richtig? => Debuggen, Fehler finden und beseitigen
    Wie man VisualStudio nutzt? => VisualStudio richtig nutzen
    Das Ganze geht auch einfacher (zumindest in diesem Beispiel von dir) ... Ohne CancellationTokenSource und ohne Try/Catch

    Spoiler anzeigen

    VB.NET-Quellcode

    1. Private Sub bgw1()
    2. For i = 1 To 10
    3. If (Abbruch) Then
    4. MessageBox.Show("Abbruch durch Benutzer")
    5. Exit Sub
    6. End If
    7. System.Threading.Thread.Sleep(500)
    8. Next i
    9. End Sub
    10. Private Async Sub berechnen_Click(sender As Object, e As EventArgs) Handles berechnen.Click
    11. Abbruch = False
    12. Await Tasks.Task.Run(AddressOf bgw1)
    13. End Sub
    14. Dim Abbruch As Boolean = False
    15. Private Sub SimpleButton1_Click(sender As Object, e As EventArgs) Handles SimpleButton1.Click
    16. Abbruch = True
    17. End Sub

    "Gib einem Mann einen Fisch und du ernährst ihn für einen Tag. Lehre einen Mann zu fischen und du ernährst ihn für sein Leben."

    Wie debugge ich richtig? => Debuggen, Fehler finden und beseitigen
    Wie man VisualStudio nutzt? => VisualStudio richtig nutzen
    Hä? Das fungiert überhaupt nicht als flag. Das ganze ist dazu da, dass man den „Fehler“ abfangen und verarbeiten kann. Wenn dein Task irgendwo verschachtelt in mehreren Klassen läuft, ist es einfacher das ganze per Try/catch zu fangen.

    Beispiele zähle ich dir nicht auf. Vor allem lässt sich nicht immer alles pauschalisieren.
    "Gib einem Mann einen Fisch und du ernährst ihn für einen Tag. Lehre einen Mann zu fischen und du ernährst ihn für sein Leben."

    Wie debugge ich richtig? => Debuggen, Fehler finden und beseitigen
    Wie man VisualStudio nutzt? => VisualStudio richtig nutzen
    jo, ich find schon dasses meist ungefähr das tut, was man sonst mit einem Flag erreichen würde.
    • Flag-Vorgehensweise: wenn man den Thread von uasserhalb beenden möchte, setzt man ein Flag, das Flag wird im Thread nachgeguckt, und dann üblicherweise die langlaufende Schleife verlassen, sodass der Thread ausläuft.
    • CancellationToken-Vorgehensweise: wenn man den Thread von uasserhalb beenden möchte, ruft man ct.CAncel auf. Im Thread wird durch .ThrowIfCancelationRequested quasi "nachgeguckt", und dann der Thread gleich richtig abgeschossen.
    Ergebnis ungefähr dasselbe.

    Bei kompliziertem, verschachtelten Code im Thread wird das mit dem Flag lästig, weil da evtl. auf verschiedenen Ebenen das Flag zu prüfen ist, und den TryCatch für andere Fehler muss man ja dennoch dazubasteln.

    Bei CancellationToken-Vorgehensweise hat man dazu noch einen TryCatch im Aufrufer, und fängt somit zusätzlich auch andere Exceptions ab, und kann die auch unterscheiden.
    Es besteht also klare Unterscheidung: ist der Thread
    • durchgelaufen
    • gecancelt
    • auf einen Fehler gelaufen
    Naja der Fakt das es durch ein Flag zu ersetzen ist in einigen Fällen, lässt es dennoch so wirken. Ansonsten sagt mir verschachtelt in mehreren Klassen nicht viel, ist das etwa bei den Quellen der Fall und bei mir nicht?
    Auch der Name Throw"If"CancellationRequested weist ja schon darauf hin, dass das Ganze abhängig von einem boolschen Wert ist.


    Von Aufzählen hat auch niemand geredet, ein einzelner Pseudocode würde mir vermutlich schon reichen, um den Unterschied zwischen der TryCatch- und der Flag-Lösung zu verstehen.
    Also nur damit man mich versteht, ich weiß nicht warum bei den Quellen jeweils mit dem TokenThrow gearbeitet wird.


    Danke @ErfinderDesRades, der Unterschied mit auslaufen und abschießen ist natürlich sinnvoll, den Flag selbst kann ich aber auch außerhalb des Threads umlegen, so wie ich den Cts.Cancel Befehl außerhalb wirke. Das If steht beide Male innerhalb des Threads sowohl bei If flag.. als auch bei ThrowIf...

    Ich verstehe es jetzt so: Bei verschachteltem Code ist es denke ich relevant dass sich die Exception von allein eigentlich durchpflanzt? Aber halt nur in der .exe nicht in Studio

    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „Haudruferzappeltnoch“ ()

    Haudruferzappeltnoch schrieb:

    Bei verschachteltem Code ist es denke ich relevant dass sich die Exception von allein eigentlich durchpflanzt?
    Ich glaube zu verstehen, was du meinst.
    Widersprechen muss ich aber als Korintenkacker: eine Exception pflanzt sich eigentlich nicht durch. Man sagt eine Exception "fliegt". Weil sie wird "geworfen" (engl: "throw")
    Gemeint ist damit, dass beim Werfen einer Exception die Methode sofort abbricht.
    wird die Exception nicht gefangen (catch), so bricht auch die Methode, die die Methode aufgerufen hat, ab.
    wird die Exception nicht gefangen, so bricht auch die Methode, die die Methode aufgerufen hat, die die Methode aufgerufen hat, ab.
    wird die Exception nicht gefangen, so bricht auch die Methode, die die Methode aufgerufen hat, die die Methode aufgerufen hat, die die Methode aufgerufen hat, ab.
    Und so weiter, bis auf die oberste Ebene, wo das Proggi dann als ganzes abbricht.



    Haudruferzappeltnoch schrieb:

    durchpflanzt? Aber halt nur in der .exe nicht in Studio
    Du meinst vermutlich den Bug der Methode Task.Run().
    "Durchpflanzen" tut sie sich durchaus (wenn damit das "fliegen" gemeint ist), nur gefangen wird sie nicht - sondern ignoriert.
    Ich hab dir doch ein Tutorial verlinkt, da wird auch darauf eingegangen, und sogar ein Workaround vorgestellt, der Abhilfe schafft.

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „ErfinderDesRades“ ()

    Ja genau das meine ich, ich war mir erst nicht sicher ob sich der Hinweis im Tutorial vielleicht nur auf die anderen Exceptions bezieht, weil an der Stelle wo nur die OperationCanceledException drin ist, schreibst du noch nichts davon das es nicht funktioniert.
    Das sieht halt so aus als ob OperationCanceledException erstmal tut, aber dann wenn du deinen CauseError einbaust nicht mehr.

    Aber jetzt hab ichs vielen Dank!
    Den Workaround hab ich auch probiert, funktioniert natürlich, aber wie gesagt war nicht sicher ob ich da was übersehen hab.