Bessere Alternative für Application.DoEvents()

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

Es gibt 9 Antworten in diesem Thema. Der letzte Beitrag () ist von Naifu.

    Bessere Alternative für Application.DoEvents()

    Hallo zusammen,
    Folgendes „Problem“: ich habe in einem Form mehrere Sub (Sub1a…4a) die völlig unabhängig voneinander über eine Datenschnittstelle die gleiche Anfrage (Sub_Frage()) an eine externe Hardware stellen. Unmittelbar nach der Anfrage starte ich in ‚Sub Frage()‘ einen Timer für einen evtl. Timeout bei der Antwort. Die Antwort der ext. Hardware dauert ein wenig. Dann gammelt das Programm in ‚Sub_Frage()‘ unnötig mit Application.DoEvents() in einer while() Schleife herum bis entweder die Antwort mittels Event oder der Timeout kommt. Mit der Antwort oder der Timout-Info geht es dann im Code Sub1b..4b weiter.
    Wie kann ich sowas besser lösen, ohne dieses Application.DoEvents()?
    Im Grunde brauche ich einen Mechanismus der dafür sorgt, dass das Programm an der Stelle des Sub_Frage()-Aufrufs in der jeweiligen Sub1..4 mit der Rückmeldung des Moduls fortgesetzt wird.
    Mein Ansatz war jetzt: Sub1a..4a rufen ‚Sub-Frage()‘ auf. Dort wird die Anfrage geschickt und es wird eine Variable gesetzt die vermerkt, welche der Sub1a…4a die Antwort möchte und ein Timer für den Timeout wird gestartet. Kommt die Antwort per Event und KEIN Timeout, wird in Event() geprüft welche Sub1a..4a die Frage gestellt hat und die Antwort möchte. Da Sub1a..4a nicht auf Rückmeldung von ‚Sub_Frage()‘ warten, wird die Abarbeitung der jeweiligen Antwort in einer separaten Sub1b..4b durchgeführt.

    @Naifu Jou.
    @roepke Der Thread arbeitet in einer Endlosschleife, er tut dabei nix (Leerlauf).
    Wenn Du Daten holen willst, sagst Du: "Daten holen" (z.B. über ein Flag oder einen Abfrage-String <> ""). Der Thread startet nun das Datenholen.
    Wenn die Daten da sind oder wenn Timeout vorbei ist, sendet der Thread ein entsprechendes Event und geht wieder in den Leerlauf.
    Feddich.
    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!
    Erinnert mich an mein internes TCP-Server-Client-Gebräu.
    Der Server (oder die Software Deiner externen Hardware) lungert im Hintergrund und wartet auf Input.
    Der Client (oder Dein Programm) erzeugt 2 Tasks. Einen Verbindungstask und einen Timeout-Task. Dann starte ich beide Tasks (nebenläufig) und wenn einer fertig ist, wird geschaut, was passiert ist. Kam ne Antwort oder hat der Timeout-Task seinen Job erledigt?
    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „VaporiZed“, mal wieder aus Grammatikgründen.

    Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht den Spekulatiusbackmodus wechseln.
    Ich habe meinen doch sehr umfangreichen Code auf das wesentliche reduziert um meine Problemstellung etwas genauer erörtern zu können.

    Spoiler anzeigen

    VB.NET-Quellcode

    1. Private Sub MacheEtwas_1()
    2. 'Hier werden erst ein paar DInge erledigt, die das eigene Form betreffen
    3. If (PruefeStatus() = False) Then
    4. Call MacheDies_1()
    5. Else
    6. Call MacheDas_1()
    7. End If
    8. 'Mache hier noch mehr Dinge die das eigene Form betreffen
    9. End Sub
    10. Private Sub MacheEtwas_2()
    11. 'Hier werden erst ein paar DInge erledigt, die das eigene Form betreffen
    12. If (PruefeStatus() = False) Then
    13. Call MacheDies_2()
    14. Else
    15. Call MacheDas_2()
    16. End If
    17. 'Mache hier noch mehr Dinge die das eigene Form betreffen
    18. End Sub
    19. Private WithEvents TimeOutTimer As New System.Timers.Timer
    20. Public Delegate Sub doSub()
    21. Dim AntwortErhalten as Boolean
    22. Dim Rueckgabewert as Boolean
    23. Private Function PruefeStatus() as Boolean
    24. AntwortErhalten = False
    25. Call RunSubWithTimeOut(AddressOf LeseStatus, 2000)
    26. 'Jetzt müsste hier eigentlich ein Returnwert stehen, dessen Zustand True/False aber noch nicht bekannt ist,
    27. 'weil dieser erst von der externen Hardware angefordert werden muss.
    28. While AntwortErhalten = False
    29. Application.DoEvents()
    30. End While
    31. Return Rueckgabewert
    32. End Sub
    33. Private Sub LeseStatus()
    34. 'hier erfolgt der Leseaufruf an die externe Hardware
    35. End Sub
    36. Private Sub RunSubWithTimeOut(d As doSub, iMilliseconds As Integer)
    37. Invoke(d)
    38. TimeOutTimer.AutoReset = False
    39. TimeOutTimer.Interval = iMilliseconds
    40. TimeOutTimer.Start()
    41. End Sub
    42. 'Antwortt die Hardware nicht, erfolgt ein Timeout
    43. Private Sub TimeOutTimer_Elapsed(sender As Object, e As System.Timers.ElapsedEventArgs) Handles TimeOutTimer.Elapsed
    44. TimeOutTimer.Stop()
    45. MessageBox.Show("Timeout")
    46. End Sub
    47. 'Die Hardware hat geantwortet, es wird ein Event ausgelöst
    48. Private Sub _Event_()
    49. TimeOutTimer.Stop()
    50. MessageBox.Show("No Timeout")
    51. AntwortErhalten = True
    52. 'Empfangene Information der externen Hardware auswerten und damit letztlich in 'MacheEtwas()' klar machen, was dort weiter zu tun ist.
    53. Rueckgabewert = 'True oder False, abhängig von der Modulantwort
    54. End Sub



    Ein WindowsForm hat die Funktionen(Subs) MacheEtwas_1…_2(). Abhängig von PruefeStatus soll entweder MacheDies_1.._2() und MacheDas_1.._2() ausgeführt werden, welche im selben WindowsForm sind.

    Mein Problem ist nun, dass das Programm in PruefeStatus() so lange verweilen muss, bis klar ist, ob dieser True oder False ist. Diese Info kommt von externer Hardware, die einige Zeit benötigt um zu antworten. Diese Antwort kommt per Event.

    In MacheEtwas_1.._2() werden zum einen noch weitere Dinge im Anschluss nach If..Else..EndIf ausgeführt, noch dazu sind MacheDies_1.._2() und MacheDas_1.._2() Bestandteil des WindowsForms. Wenn ich MacheEtwas_1.._2() mittels Invoke ausführe läuft ja nur diese Funktion losgelöst vom Rest, oder? MacheDies_1.._2() und MacheDas_1.._2() sind ja nur im Haupthread was passiert bei deren Aufruf innerhalb von Invoke.

    Bzw. wie ist das dann, wenn eine per Invoke aufgerufene Funktion bei PruefeStatus() ankommt und wiederum diese Funktion startet? Wenn dies ebenfalls per Invoke geschieht, sollte es doch kein Problem mehr sein. Wenn ich das richtig verstehe, dann läuft ja im Grunde das Hauptprogramm weiter, MacheEtwas_1.._2() sind davon losgelöst. PruefeStatus() wäre das ebenfalls. D.h. wenn dort ein Application.DoEvents() steht, sollte es den Rest der Anwendung nicht beeinflussen. Kommt dann aber die Antwort per Event muss dieses zuvor von PruefeStatus() abonniert werden, da dies ja in einem extra Thread läuft.

    Dieser Beitrag wurde bereits 4 mal editiert, zuletzt von „roepke“ ()

    Iwie ist mir dein letzter Post verlustig gegangen.
    In deinem PseudoCode kann ich nicht die Problem Stellung deines erten Posts erkennen, es fehlt mir auch die HW Simulation.
    Zum Problem:
    Das Ganze soll in einem Form ablaufen?
    Jede der 4 Sub kann den HW Request auslösen?

    Als erstes benötigen wir eine HW Simulation in Form einer klasse, die Verzögerung kannst du mit einem Timer realisieren.
    Ist der Timer fertig dann löst das einen event aus.
    Das Ergebnis des HW Request packst du in eine eingen Klasse die von EventArgs erbt und im Event übergeben wird.
    Die Klasse würde ich HWSim taufen.
    Die sollte eine Sub Namens Request haben.
    Den Event könnte man Response benamen.
    Und ResponseEventArgs für die die Daten.
    Mach mal und poste den Code der Simulation.
    Dann sehen wir weiter. ;o)
    Keine Antwort, scheint ja nicht so wichtig zu sein.
    Habe dir mal was zusammen gestrickt.

    VB.NET-Quellcode

    1. ​Public Class Form1
    2. Private HWSimu As New HWSim
    3. Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    4. HWSimu.Request(AddressOf Sub1)
    5. HWSimu.Request(AddressOf Sub2)
    6. HWSimu.Request(AddressOf Sub3)
    7. HWSimu.Request(AddressOf Sub4)
    8. End Sub
    9. Private Sub Sub1(sender As Object, e As EventArgs)
    10. Dim hw As HWSim = DirectCast(sender, HWSim)
    11. MessageBox.Show("Sub1" & vbCrLf & hw.Data)
    12. End Sub
    13. Private Sub Sub2(sender As Object, e As EventArgs)
    14. Dim hw As HWSim = DirectCast(sender, HWSim)
    15. MessageBox.Show("Sub2" & vbCrLf & hw.Data)
    16. End Sub
    17. Private Sub Sub3(sender As Object, e As EventArgs)
    18. Dim hw As HWSim = DirectCast(sender, HWSim)
    19. MessageBox.Show("Sub3" & vbCrLf & hw.Data)
    20. End Sub
    21. Private Sub Sub4(sender As Object, e As EventArgs)
    22. Dim hw As HWSim = DirectCast(sender, HWSim)
    23. MessageBox.Show("Sub4" & vbCrLf & hw.Data)
    24. End Sub
    25. End Class


    VB.NET-Quellcode

    1. Public Class HWSim
    2. Private handlerQ As New Queue(Of EventHandler)
    3. Private isRequestRunning As Boolean = False
    4. Property Data As String
    5. Private Sub [AddHandler](hEv As EventHandler)
    6. handlerQ.Enqueue(hEv)
    7. End Sub
    8. Public Sub Request(hEv As EventHandler)
    9. [AddHandler](hEv)
    10. If Not isRequestRunning Then
    11. isRequestRunning = True
    12. Dim th As New Threading.Thread(AddressOf StartReqeuest)
    13. th.IsBackground = True
    14. th.Start()
    15. End If
    16. End Sub
    17. Private Sub StartReqeuest()
    18. Dim sw As New Stopwatch
    19. sw.Start()
    20. While sw.ElapsedMilliseconds < 1000
    21. 'Warten auf die HW
    22. End While
    23. While handlerQ.Count <> 0
    24. _Data = handlerQ.Count.ToString
    25. handlerQ.Dequeue.Invoke(Me, New EventArgs)
    26. End While
    27. isRequestRunning = False
    28. End Sub
    29. End Class


    Ist nur ein Weg, das kannst du dann ja so anpassen wie du es benötigst.
    Viel Spass damit.