Problem mit Timer in einer Klasse

  • VB.NET

Es gibt 35 Antworten in diesem Thema. Der letzte Beitrag () ist von egon.

    Problem mit Timer in einer Klasse

    In einem anderen jetzt schon sehr langen und unübersichtlichen Thema wird ein Programm von mir bearbeitet.

    Habe ich ein kleineres Problem mit einem Timer, welches sich getrennt vom Rest besprechen lässt:
    Die Klasse Testklasse soll ein größeres Programm simulieren in dem drei zeitraubende Aufgaben abgearbeitet werden sollen, ohne dass das Programm hängenbleiben darf:

    Button 1
    Hier wird einfach nur der Timer getestet. Hier funktioniert scheinbar alles.

    Button 2
    Hier wird das größere Programm simuliert und da bleibt der Timer an der markierten Stelle hängen:
    Die einzelnen Schritte:
    1. Notepad starten und warten bis es geschlossen wird
    2. Eine Sekunde warten -->> Das Warten der einen Sekunde funktioniert nicht.
    3. Notepad starten und warten bis es geschlossen wird


    VB.NET-Quellcode

    1. Option Strict On
    2. Public Class Form1
    3. Dim test As New Testklasse
    4. Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    5. test.timertest()
    6. End Sub
    7. Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
    8. test.start_queue()
    9. End Sub
    10. End Class

    VB.NET-Quellcode

    1. Public Class Testklasse
    2. Private WithEvents prc As Process
    3. Private WithEvents mytimer As Timer
    4. Private Reihenfolge_index As New Queue(Of Integer)
    5. Private Reihenfolge_info As New Queue(Of String)
    6. Public Sub start_queue()
    7. 'Queue füllen
    8. For i = 0 To 2
    9. Reihenfolge_index.Enqueue(i)
    10. Next
    11. Reihenfolge_info.Enqueue("eins")
    12. Reihenfolge_info.Enqueue("zwei")
    13. Reihenfolge_info.Enqueue("drei")
    14. timertest() 'Hier funktioniert der Timertest
    15. End Sub
    16. Public Sub Ablaufsteuerung()
    17. If Reihenfolge_index.Count > 0 Then
    18. Select Case Reihenfolge_index.Dequeue()
    19. Case 0
    20. Console.Beep(1500, 200)
    21. prc = New Process
    22. prc.StartInfo.FileName = "notepad.exe"
    23. prc.EnableRaisingEvents = True
    24. prc.Start()
    25. Console.WriteLine(Reihenfolge_info.Dequeue())
    26. Case 1
    27. timertest() 'Hier funktioniert der Timertest nicht !!!!!!!!!!!!!!!!!!!!!
    28. Console.WriteLine(Reihenfolge_info.Dequeue())
    29. Case 2
    30. prc = New Process
    31. prc.StartInfo.FileName = "notepad.exe"
    32. prc.EnableRaisingEvents = True
    33. prc.Start()
    34. Console.WriteLine(Reihenfolge_info.Dequeue())
    35. Case Else
    36. Console.WriteLine("error")
    37. End Select
    38. End If
    39. End Sub
    40. Private Sub Timer_exit(sender As Object, e As EventArgs) Handles mytimer.Tick
    41. mytimer.Stop()
    42. Console.Beep(100, 200)
    43. Ablaufsteuerung()
    44. End Sub
    45. Private Sub prc_exit(sender As Object, e As EventArgs) Handles prc.Exited
    46. Ablaufsteuerung()
    47. End Sub
    48. Public Sub timertest()
    49. Console.Beep(500, 200)
    50. mytimer = New Timer
    51. mytimer.Interval = 1000
    52. mytimer.Start()
    53. End Sub
    54. End Class


    Wo kann der Fehler liegen? Rufe ich die Klasse oder den Timer falsch auf?
    Ich konnte jetzt nur nach langem Hin und Her schlussfolgern, dass Process.Exited dem Timer in die Beine grätscht. Daher folgender Vorschlag (hab ich erfolgreich probiert): Ersetze den Timer durch eine asynchrone Methode:
    Spoiler anzeigen

    VB.NET-Quellcode

    1. Public Class Form1
    2. Dim test As New Testklasse
    3. Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    4. test.timertest()
    5. End Sub
    6. Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
    7. test.start_queue()
    8. End Sub
    9. End Class
    10. Public Class Testklasse
    11. Private WithEvents prc As Process
    12. Private Reihenfolge_index As New Queue(Of Integer)
    13. Private Reihenfolge_info As New Queue(Of String)
    14. Public Sub start_queue()
    15. 'Queue füllen
    16. For i = 0 To 2
    17. Reihenfolge_index.Enqueue(i)
    18. Next
    19. Reihenfolge_info.Enqueue("eins")
    20. Reihenfolge_info.Enqueue("zwei")
    21. Reihenfolge_info.Enqueue("drei")
    22. timertest() 'Hier funktioniert der Timertest
    23. End Sub
    24. Public Sub Ablaufsteuerung()
    25. If Reihenfolge_index.Count > 0 Then
    26. Select Case Reihenfolge_index.Dequeue()
    27. Case 0
    28. Console.Beep(1500, 200)
    29. prc = New Process
    30. prc.StartInfo.FileName = "notepad.exe"
    31. prc.EnableRaisingEvents = True
    32. prc.Start()
    33. Console.WriteLine(Reihenfolge_info.Dequeue())
    34. Case 1
    35. timertest() 'Hier funktioniert der Timertest nicht !!!!!!!!!!!!!!!!!!!!!
    36. Console.WriteLine(Reihenfolge_info.Dequeue())
    37. Case 2
    38. prc = New Process
    39. prc.StartInfo.FileName = "notepad.exe"
    40. prc.EnableRaisingEvents = True
    41. prc.Start()
    42. Console.WriteLine(Reihenfolge_info.Dequeue())
    43. Case Else
    44. Console.WriteLine("error")
    45. End Select
    46. End If
    47. End Sub
    48. Private Sub TimerTickReplacement()
    49. Console.Beep(100, 200)
    50. Ablaufsteuerung()
    51. End Sub
    52. Private Sub prc_exit(sender As Object, e As EventArgs) Handles prc.Exited
    53. Ablaufsteuerung()
    54. End Sub
    55. Public Async Sub timertest()
    56. Console.Beep(500, 200)
    57. Await Threading.Tasks.Task.Delay(1000)
    58. TimerTickReplacement()
    59. End Sub
    60. End Class

    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.
    @RdG: In Wirklichkeit werden externe Programme gestartet die ein Messgerät ansteuert und dann muss gewartet werden bis die Messung abgeschlossen ist. Wenn dann eine Zeitspanne (z.B. 1s) verstrichen ist müssen die Messergebnise wieder abgeholt werden, usw. Eine Gesamtmessung über alle Messpunkte und Kurven kann zw. 1 bis 30 Minuten dauern, in dieser Zeit soll der Computer nicht blockiert werden. Der Ansatz von VaporiZed hat mir da sehr geholfen und löst meine Problem - auch wenn ich noch nicht verstehe warum das mit dem normalen Timer nicht funktioniert.
    @egon Wenn das über eine Instanz der Process-Klasse funktioniert, lass Dir doch vom Excited-Event mitteilen, dass die Daten gespeichert und das Messprogramm beendet ist.
    Hast Du von diesem Messprogramm die Quellen?
    Wenn ja, kannst Du doch eine Socket-Verbindung zwischen beiden Programmen aufbauen und sogar die Daten selbst übertragen.
    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!
    >> Wenn das über eine Instanz der Process-Klasse funktioniert, lass Dir doch vom Excited-Event mitteilen, dass die Daten gespeichert und das Messprogramm beendet ist.
    Kannst du mir bitte ein Beispiel geben.

    >> Hast Du von diesem Messprogramm die Quellen? Wenn ja, kannst Du doch eine Socket-Verbindung zwischen beiden Programmen aufbauen und sogar die Daten selbst übertragen.
    Es ist nicht mein Programm. Ich könnte aber den Programierer (USA) fragen. Nur weiß ich nicht die "richtigen" Fragen. Das kleine Programm liegt mir als *.exe Datei vor. In dem Ordner ist auch eine *.cpp Datei mit gleichem Namen.
    Ich könnte dir die beiden Dateien per Mail zukommen lassen (wenn es hilft), stelle sie aber nicht öffentlich hier ins Forum ohne den Autor zu fragen.

    [Nachtrag] Es sieht so aus, als ob ich die gesamten Daten habe. Es scheint in einer der C-Sprachen geschrieben worden sein. Die Frage ist nur, ob sich der Aufwand lohnt. Da habe ich keine Ahnung.
    Wenn Daten ausgelesen werden, schreibt das andere Programm die Daten in eine txt.Datei die ich dann anschließend auswerte. Das schöne an dem Programm ist, dass es für mich die Komunikation mit dem Messinterface und dem Messgerätebus fehlerfrei übernimmt. Das ist schon mal eine Baustelle, die ich nicht beackern muss ;-). Wenn die Daten aber leicht direkt übernommen/eingelesen werden können, wäre das schon sehr elegant.

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

    egon schrieb:

    Kannst du mir bitte ein Beispiel geben.

    VB.NET-Quellcode

    1. Public Class Form1
    2. Private WithEvents prc As Process
    3. Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    4. prc = New Process
    5. prc.StartInfo.FileName = "notepad.exe"
    6. prc.EnableRaisingEvents = True
    7. prc.Start()
    8. End Sub
    9. Private Sub prc_exit(sender As Object, e As EventArgs) Handles prc.Exited
    10. MessageBox.Show("Prozess beendet")
    11. End Sub
    12. End Class
    =====
    Arbeitet Ihr mit den amerikanischen Kollegen zusammen oder habt Ihr was von denen gekauft?
    Es ist in jedem Falle besser, das von denen machen zu lassen, sie zumindest darum zu bitten, damit Ihr nicht auf einer Solo-Version sitzt und von späteren Updates abgeschnitten seid.
    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!
    Hmmmm. Dann habe ich dich falsch verstanden. Ich dachte, dass du eine andere Lösung vorschlägst, als ich schon verwende... Nur verträgt sich der Excited-Event nicht mit dem Timer-Event. Keine Ahnung warum. Das ist hier die große Frage und deren Antwort würde mich sehr interessieren. Wenn es ein Programmierfehler von mir ist, würde ich ihn sonst auch an andere Stelle fälschlicherweise einbauen :( .
    Der asynchrone Timer von VaporiZed löst das Problem eben etwas anders. Was Await Threading.Tasks.Task.Delay(1000) genau macht habe ich noch nicht verstanden - nur dass es funktioniert...

    Ich schreibe mein eigenes Programm nur für mich persönlich.
    Von Updates der Hilfsoftware werde ich ziemlich sicher nicht abgeschnitten sein, da das Programm schon seit vielen Jahren ausgereift ist. Es steuert einen jahrzehntealten Gerätebus (GPIB) der international seit ungefähr 50 Jahren für Labormessgeräte verwendet wird. Der Bus wird auch noch in vielen Jahren existieren (schon um kompatibel mit älteren Geräten zu bleiben). Die übermittelten Parameter sind eigentlich nur abhängig vom verwendeten Messgerät. Wenn ich z.B. das Messgerät irgendwann mal austausche muss ich meine Software eben leicht anpassen. Auch kann ich den Autor bei Fragen per Email um Hilfe bitten. Die Software stellt er frei im Netz zur Verfügung.
    Ich werde ihn aber nicht ansprechen, damit er seine Software so umschreibt, nur damit ich eleganter auf sein Hilfsprogramm zugreifen kann. Meine jetziges Vorgehen mit der Zwischenspeicherung in einer txt-Datei funktioniert ja fehlerfrei - auch wenn es nicht elegant sondern nur pragmatische ist ;) .

    egon schrieb:

    Was ... genau macht habe ich noch nicht verstanden
    Dort wird in einem anderen Thread einfach eine Sekunde gewartet.
    Jetzt muss ich allerdings noch mal nachhaken:
    Wozu brauchst Du den Timer genau?
    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!
    Ich hab das Problem gefunden, indem ich Timer und Process einmal als CE im Designer auf mein Formular zog und modifizierte und dann Gegentests mit den selbsterstellten Instanzen machte. Dabei entdeckte ich, dass es im Designer-Code einen wichtigen Zusatz gab, nämlich dass das Process-CE synchron zum Formular läuft: Process1.SynchronizingObject = Me.
    Der vollständig korrigierte Code aus Post#1 würde folgendermaßen lauten (ich sah keinen Sinn darin, prc und mytimer immer neu zu instanziieren):
    Spoiler anzeigen

    VB.NET-Quellcode

    1. Public Class Form1
    2. Dim test As New Testklasse
    3. Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    4. test.start_queue(Me)
    5. End Sub
    6. End Class
    7. Public Class Testklasse
    8. Private WithEvents prc As New Process
    9. Private WithEvents mytimer As New Timer
    10. Private Reihenfolge_index As New Queue(Of Integer)
    11. Private Reihenfolge_info As New Queue(Of String)
    12. Public Sub start_queue(f As Form)
    13. 'Queue füllen
    14. prc.StartInfo.FileName = "notepad.exe"
    15. prc.EnableRaisingEvents = True
    16. prc.SynchronizingObject = f
    17. mytimer.Interval = 1000
    18. For i = 0 To 2
    19. Reihenfolge_index.Enqueue(i)
    20. Next
    21. Reihenfolge_info.Enqueue("eins")
    22. Reihenfolge_info.Enqueue("zwei")
    23. Reihenfolge_info.Enqueue("drei")
    24. timertest() 'Hier funktioniert der Timertest
    25. End Sub
    26. Public Sub Ablaufsteuerung()
    27. If Reihenfolge_index.Count > 0 Then
    28. Select Case Reihenfolge_index.Dequeue()
    29. Case 0
    30. Console.Beep(1500, 200)
    31. prc.Start()
    32. Console.WriteLine(Reihenfolge_info.Dequeue())
    33. Case 1
    34. timertest() 'Hier funktioniert der Timertest nicht !!!!!!!!!!!!!!!!!!!!!
    35. Console.WriteLine(Reihenfolge_info.Dequeue())
    36. Case 2
    37. prc.Start()
    38. Console.WriteLine(Reihenfolge_info.Dequeue())
    39. Case Else
    40. Console.WriteLine("error")
    41. End Select
    42. End If
    43. End Sub
    44. Private Sub Timer_exit(sender As Object, e As EventArgs) Handles mytimer.Tick
    45. mytimer.Stop()
    46. Console.Beep(100, 200)
    47. Ablaufsteuerung()
    48. End Sub
    49. Private Sub prc_exit(sender As Object, e As EventArgs) Handles prc.Exited
    50. Ablaufsteuerung()
    51. End Sub
    52. Public Sub timertest()
    53. Console.Beep(500, 200)
    54. mytimer.Start()
    55. End Sub
    56. End Class



    Im MSDN ist es zwar beschrieben, schlau werde ich allerdings nicht daraus, da der ganze Spaß ja hier in einer eigenen Klasse abläuft, aber Spekulatius: Da die Klassenprozedur aus einem CE-EventHandler heraus gestartet wurde, gibt's mal wieder Thread-Probleme ohne das SynchronizingObject.

    Bzgl. Async und Await: siehe Thread von z.B. EdR.
    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.

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

    Da wart ihr mal wieder fleixig. Über die Veränderungen und die Links von VaporiZed muss ich noch nachdenken.

    >>Wozu brauchst Du den Timer genau?
    Zuerst wird mein Messgerät mit den notwendigen Einstellungen versehen und beginnt zu messen. Eine Messung kann z.B. 1 Sekunde dauern. Mir wird aber nicht mitgeteilt, dass die Messung beendet ist. Daher muss ich selbst wissen welche Messdauer ich dem Gerät vorgegen haben und dann entsprechend warten bevor ich die Ergebnisse dann anschließend abrufe. Wenn ich die Messasdaten zufrüh abrufe bekomme ich teilweise auch noch alte/falsche Messdaten.
    Hm ... werden die SpektrumAnalyser-Daten nach und nach in die Textdatei geschrieben, sodass Du sie auch zu früh abrufen könntest? Ich hätte jetzt vermutet, dass die Datei während des Schreibens nicht zugänglich ist und eine Exception kommt, wenn man es trotzdem probiert. Ansonsten ggf. Experimente mit FileInfo.LastWriteTime oder der Dateigröße machen, vielleicht kannst Du so erfahren, wann der Schreibvorgang beendet ist. Ich schätze mal auch, dass der SpektrumAnalyser-Aufruf eine fire-and-forget-geschichte ist. Man gibt dem AnalyserGerät die Daten und das Programm macht weiter, während das Gerät seine Daten nebenbei in die Txt-Datei ausspuckt. Vielleicht kannst Du Dich aber auch notfalls in den Datenstrom einklinken und schauen, wann dort nix mehr übertragen wird.
    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.
    Das Hilfsprogramm ist erst dann beendet wenn alle Daten in das txt-File geschrieben worden sind. Dieser Vorgang dauert rund drei Sekunden. Eigentlich bringt es keinen Zeitvorteil wenn ich mich in den Datenstrom einklinke, da die Auswertung sehr schnell geht. Es werden 401 Einzeldaten geliefert aus denen ich den Median bilde. Das geht extrem schnell: sortieren und dann das mittlere Element nehmen. Mich stört auch das txt-File nicht, auch wenn sich vielleicht einigen Programmieren bei dieser Zwischenlösung die Fußnägel hochklappen. Das ist eine Nebenschauplatz mit dem Einbinden den RfG ins Speil gebracht hat. Ich glaube nicht dass es sich lohnt noch mehr Mühe darauf zu verwenden. Die Lösung mit dem TXT-File wird auch die Variante sein, die fuktioniert, wenn der Programmierer seine Software doch noch weiter updatet.

    Wenn ich dem Messgerät (SpektrumAnalyser) Einstellungen übermittle oder eine Messung starte ist es eine fire-and-forget-Geschichte ist. Wenn ich Messdaten abrufe wird das Hilfstprogramm erst dann beendet wenn die neuen Daten alle vollständig ins Textfile geschrieben worden sind. Beschleunigen kann ich die Kommunikkation nur etwas durch ein noch schnelleres Interface, was keine Änderungen am Programm nach sich ziehen würde.

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

    egon schrieb:

    Das Hilfsprogramm ist erst dann beendet wenn alle Daten in das txt-File geschrieben worden sind.
    Da nimm doch einfach einen FileSystemWatcher, da bekommst Du ein Event, wenn die Datei fertig geschrieben ist.
    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!
    Was spricht gegen prc.EnableRaisingEvents = True und Private Sub prc_exit(sender As Object, e As EventArgs) Handles prc.Exited?

    Aber unabhängig von meiner vorhergehenden Frage: Wie wird ein FileSystemWatcher richtig eingesetzt? Welchen Vorteil hat dieser Weg?

    Viel mehr interessiert mich aber der Lösungsansatz aus Beitrag #11. Warum hilft prc.SynchronizingObject = f in Zeile 17?

    Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „egon“ ()

    Wie dem verlinkten MSDN-Beitrag zu entnehmen ist, kann es passieren, dass ohne die genannte Textzeile das Process.Exited-Ereignis in einem anderen Thread entsteht als der Rest des Programms bzw. die Klasse läuft. Das führt entweder zu einer Fehlermeldung/Exception oder einfach dazu, dass sich das Programm nicht so verhält, wie man es erwartet, eben wie im hiesigen Fall:

    MSDN schrieb:

    When SynchronizingObject is null, methods that handle the Exited event are called on a thread from the system thread pool. For more information about system thread pools, see ThreadPool.
    When the Exited event is handled by a visual Windows Forms component, such as a Button, accessing the component through the system thread pool might not work, or might result in an exception. Avoid this by setting SynchronizingObject to a Windows Forms component, which causes the methods handling the Exited event to be called on the same thread on which the component was created.

    btw: Ich habe mich hier explizit nur um das genannte Phänomen gekümmert, weil ich davon fasziniert war. Komplett andere Lösungswege sind bei mir erstmal außen vor, da ließe sich einiges machen
    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 weiss jetzt nicht, welche Alternativen dir verfügbar sind (18 posts nicht studiert), aber Backgroundworker ist Müll - immer.

    Und wenn es Möglichkeiten gibt, Threading zu vermeiden, sind diese immer vorzuziehen (naja, zu 95%).

    Hmm - grade eine Prozessteuerung wäre evtl. mit Async doch sehr elegant. Weil das kann man programmieren wie einen sequentiellen Ablauf, also wenn du 10 Programmschritte abzuarbeiten hast, kannste das mit Async einfach in einer Schleife abhandeln, wo man mittm Timer ziemlich rumwerkeln müsste, um die Schleife in eine Art Status-Automaten zu übertragen.

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