Alternative zu "While BackgroundWorkerXY.IsBusy -> Sleep -> Application.DoEvents()"

  • VB.NET

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

    Alternative zu "While BackgroundWorkerXY.IsBusy -> Sleep -> Application.DoEvents()"

    Hallo zusammen,

    gibt es eine Alternative zu Quick&Dirty-Lösungen wie:

    VB.NET-Quellcode

    1. While BackgroundWorkerXY.IsBusy
    2. System.Threading.Thread.Sleep(10)
    3. Application.DoEvents()
    4. End While


    Hintergrund
    Wie sich erahnen lässt will ich damit mein Programm daran hindern, dass es weiterläuft ohne das der Backgroundworker seine Tätigkeit (BGW fragt alle 20ms einen Portpin/Taster eines µC ab, bis die Taste fünf mal gedrückt wurde) abgeschlossen hat.

    Warum asynchron über BGW?

    Damit meine UserForm nicht hängt. Diese zeigt mittels Label die Anzahl der Tastendrücke an.



    Ich muss gestehen, dass ich es fast so stehen gelassen hätte. :D Allerdings, wie zu erahnen war, dreht Application.DoEvents() völlig durch und der Debugger gibt mir eine System.ExecutionEngineException aus. :S

    Nun wollte ich fragen was da gängige Praxis ist, da ich mich mit Multithreading bisher nicht so befasst habe. Wahrscheinlich ist meine Vorgehensweise zu prozedural?
    Eigentlich gäbe es ja keinen driftigen Grund diese Operation asynchron auszuführen, da ich ja vorhabe auf Abschluss des BGW zu warten. Wäre es vielleicht sinnvoller das über einen normalen Thread zu lösen?

    Danke für eure Mithilfe im Voraus! :thumbsup:
    @vanitas Dein Code ist Grütze.
    Wenn während der Arbeit des Thfreads an der GUI nix gemacht werden darf, disable die betreffenden Controls.
    Wenn Du mit Framework >= 4.5 arbeitest, nimm einen Task und warte dann mit myTask.Wait() auf dessen Fertigstellung.
    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!

    VB.NET-Quellcode

    1. Private Sub TestTaskRun(ByVal Taste As Byte)
    2. Dim t As Task = Task.Run(Sub()
    3. Dim anzahlTastendruck As Byte = 0
    4. Dim antwort As String = ""
    5. Do Until anzahlTastendruck = 5
    6. antwort = Controller.TasteAbfragen(Taste, com) 'com = COM-Port Objekt
    7. If antwort = "bit 1" Then
    8. anzahlTastendruck = CByte(anzahlTastendruck + 1)
    9. Select Case Taste
    10. Case frm_Main.Taste.Hoch
    11. Me.lblTastenHochAnzahlTastendruck.Text = String.Format("{0}/5", anzahlTastendruck)
    12. Case frm_Main.Taste.Runter
    13. Me.lblTastenRunterAnzahlTastendruck.Text = String.Format("{0}/5", anzahlTastendruck)
    14. Case frm_Main.Taste.Links
    15. Me.lblTastenLinksAnzahlTastendruck.Text = String.Format("{0}/5", anzahlTastendruck)
    16. Case frm_Main.Taste.Rechts
    17. Me.lblTastenRechtsAnzahlTastendruck.Text = String.Format("{0}/5", anzahlTastendruck)
    18. End Select
    19. End If
    20. System.Threading.Thread.Sleep(500)
    21. Loop
    22. anzahlTastendruck = 0
    23. End Sub)
    24. t.Wait()
    25. End Sub


    Habe das so aus dem MSDN. Mit Tasks habe ich noch nicht gearbeitet. Wie gesagt, sobald ich die Sub ausführe geht meine UserForm in die Knie.
    mir scheint, man sollte hier was mit Timern konzipieren - aber keine Threads starten, kein Backgroundworker oder was mit Threads oder Tasks versuchen.

    Aber du müsstest mal erklären, was du machen willst.

    Insbesondere ist wichtig, wie der ComPort tickt: Antwortet der direkt mit Rückgabewert, oder antwortet er indirekt, indem ein Callback oder ein Eventhandler aufgerufen wird?
    Wenn letzteres - geschieht das im MainThread oder asynchron?
    Hallo,

    der COM-Port antwortet direkt mit Rückgabewert.

    Ich möchte in einer Schleife eine der Tasten abfragen.

    Dazu fragt der COM Port einen Portpin vom µC ab:

    Bisteda?
    -Nö
    Bisteda?
    -Nö
    Bisteda?
    -Nö
    Bisteda?
    -Nö
    Bisteda?
    -Nö

    Wenn ich den Taster dann drücke :

    Bisteda?
    -Jo

    Sprich ich bekomme "bit 1" zurück. Jetzt soll auf meiner UserForm ein Label den Tastendruck anzeigen, also 1/5. Wenn ich die Taste dann 5 mal gedrückt habe steht da 5/5.


    Mit BGW und meinem Quick and Dirty Code aus Post #1 hat das zeitweise funktioniert. Bis aufeinmal aus heiterem Himmel die System.EngineExecutionException geworfen wurde. Der Debugger zeigt dann auf das Application.DoEvents() in meiner While-Schleife.
    @vanitas Dann solltest Du in einer "normalen" GUI im KeyDown-Event der Form (.KeyPreview = True) die Tastendrücke auffangen und entsprechende Prozeduren aufrufen.
    Weitere Tastendrücke werden per Flag ignoriert (ggf. entsprechende Ausgabe).
    Je nach dem, wie lange die dauern, lässt Du die im GUI-Thread oder halt als Task laufen.
    Die Prozedur meldet ihr Beenden, worauf hin das Tastatur-Sperr-Flag zurückgesetzt wird.
    Feddich. :D
    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!

    vanitas schrieb:

    über die serielle Schnittstelle
    Und wo ist da der Unterschied, außer dass das SerialPort in einem anderen Thread arbeitet?
    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!
    Die Serielle Schnittstelle geht über einen Schnittstellenwandler an einen klassischen Master-Slave Bus. Ich muss über die Schnittstelle pollen: "Taste XY bist du gedrückt?".

    Wie wäre denn Async und Await auf meinen oben gepostete Methode anzuwenden?
    Es hat ja schonmal funktioniert, ich hätte nur nicht gedacht das Application.DoEvents() solche Probleme machen kann ^^

    vanitas schrieb:

    Ich muss über die Schnittstelle pollen
    bedeutet, dass Du das in einem Timer abfragst. Dafür brauchst Du kein Async und Await.
    Und:
    Bring Dein Programm zunächst ohne separate Threads und Tasks und Co zum laufen, auch wenn die GUI einfriert.
    Erst wenn der Ablauf sicher läuft, befassen wir uns mit solch Optimierungen :!:
    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!
    @RodFromGermany

    Hast du dir mal den Code angeguckt den ich gepostet habe? Eigentlich sollte der Task ha asynchron laufen. Trotzdem friert mir beim Ausführen die GUI ein.
    In der Methode ist doch schon eine Art Timer drin, sprich nach jedem Schleifendurchlauf wartet er ja 500ms bis er wieder pollt. Kann man denn mit dem Code garnichts anfangen ?
    @vanitas Anfangen nix, da ich das ohne Hardware nicht nachvollziehen kann.
    Aber:
    Schreibe keine anonymen Methoden länger als 2 Zeilen :!: :!: :!:
    Mach da eine separate Prozedur draus, denn die kannst Du debuggen (anonyme Methoden nicht), gugst Du hier.
    System.Threading.Thread.Sleep(500) streich ersatzlos und ruf die Prozedur in einem Timer mit einem .Interval = 500 auf.
    Falls Du diese Prozedur bereits in einem Timer-Tick-Event aufrufst, ist dieses Sleep() der Grund für das Einfrieren.
    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!
    Habe mal einen Blick in den Programmcode geworfen.
    Anweisungen innerhalb einer Task wie

    Quellcode

    1. Me.lblTastenHochAnzahlTastendruck.Text = ...

    sollten eigentlich folgende Ausnahme nach sich ziehen:

    System.InvalidOperationException wurde nicht von Benutzercode behandelt.
    Message=Ungültiger threadübergreifender Vorgang: Der Zugriff auf das Steuerelement lblTastenHochAnzahlTastendruck erfolgte von einem anderen Thread als dem Thread, für den es erstellt wurde.

    Erhälst du diese Fehlermeldung?
    Wenn nein, hast du irgendwie mit Control.CheckForIllegalCrossThreadCalls rumgemurkst, um die Ausnahme zu unterdrücken?

    Anbei ein Lösungsvorschlag, um Controls direkt aus einer Task oder einem Timer heraus anzusprechen:

    Quellcode

    1. Invoke( Sub() Me.lblTastenHochAnzahlTastendruck.Text = ... )

    An manchen Tagen gibt es zu allem Überfluss auch noch Ärger!

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