Problem mit Programmlogik

  • VB.NET

Es gibt 21 Antworten in diesem Thema. Der letzte Beitrag () ist von SAR-71.

    Problem mit Programmlogik

    Moin,

    wahrscheinlich ist es einfach zu spät, aber ich finde meinen Fehler einfach nicht.

    Folgende Situation: Ich habe zwei FileSystemWatcher - beide werden kurz nacheinander angeworfen und bei beiden tritt das Ereignis etwa zur gleichen Zeit auf (könnte aber auch andere Zeiten haben).
    Erst wenn BEIDE fertig sind, wird der nächste Sub aufgerufen.

    Folgender Sub bringt die Sache ins Rollen:

    VB.NET-Quellcode

    1. Sub Stein_beginnt_zu_rollen()
    2. AlleFertig = false
    3. watcher1 = New IO.FileSystemWatcher("Datei1")
    4. Shell("externe Anwendung 1")
    5. watcher1.EnableRaisingEvents = True
    6. watcher2 = New IO.FileSystemWatcher("Datei2")
    7. Shell("externe Anwendung 2")
    8. watcher2.EnableRaisingEvents = True
    9. End Sub


    Und hier die Subs der Watcher.

    VB.NET-Quellcode

    1. Sub watcher1(ByVal sender As Object, ByVal e As System.IO.FileSystemEventArgs) Handles watcher1.Deleted
    2. 'Verarbeitung
    3. If AlleFertig Then
    4. nextSub()
    5. End If
    6. AlleFertig = True
    7. End Sub
    8. Sub watcher2(ByVal sender As Object, ByVal e As System.IO.FileSystemEventArgs) Handles watcher2.Deleted
    9. 'Verarbeitung
    10. If AlleFertig Then
    11. nextSub()
    12. End If
    13. AlleFertig = True
    14. End Sub



    Beide Watcher machen ganz normal die Verarbeitung. Alles durchgetestet.
    Aber der "nextSub" wird nie aufgerufen. Jetzt stellt sich mir die Frage: Rein logisch müsste "nextSub" aufgerufen werden - praktisch aber nicht, jedoch warum?




    Mfg.
    SAR
    Hey,

    mach mal aus dem da.

    VB.NET-Quellcode

    1. Sub watcher1(ByVal sender As Object, ByVal e As System.IO.FileSystemEventArgs) Handles watcher1.Deleted
    2. 'Verarbeitung
    3. If AlleFertig Then
    4. nextSub()
    5. End If
    6. AlleFertig = True
    7. End Sub


    das da.

    VB.NET-Quellcode

    1. Sub watcher1(ByVal sender As Object, ByVal e As System.IO.FileSystemEventArgs) Handles watcher1.Deleted
    2. 'Verarbeitung
    3. AlleFertig = True
    4. If AlleFertig Then
    5. nextSub()
    6. End If
    7. End Sub


    Die nextSub kann nie aufgerufen werden, da Du das Flag erst nach der If-Abfrage auf True setzt. Logisch.

    EDIT: Aber da Du nicht wohl nicht weisst, wann jeder fertig ist, sollte vorher der Eine auf den Anderen warten.
    Die Unendlichkeit ist weit. Vor allem gegen Ende. ?(
    Manche Menschen sind gar nicht dumm. Sie haben nur Pech beim Denken. 8o
    So kann ich mir ja alles sparen. Dann brauch ich nicht die Variable. :P

    Der Sinn dahinter ist, wenn watcher1 fertig ist, dass watcher2 (wenn er dann auch fertig ist) den Sub aufruft.
    Und nicht, wenn watcher1 fertig ist, den Sub aufruft (obwohl w2 nicht soweit ist) und danach würde w2 auch nochmal den Sub aufrufen.

    @Edit: Das Warten würde ich gerne vermeiden. Dass würde bedeuten das ich wieder neue Threads machen müsste etc.


    Mfg.
    SAR
    Dann werden wohl beide so ziemlich zur gleichen Zeit fertig und haben den If-Block schon beide hinter sich, bevor einer der Beiden das Flag auf True setzt.
    Die Unendlichkeit ist weit. Vor allem gegen Ende. ?(
    Manche Menschen sind gar nicht dumm. Sie haben nur Pech beim Denken. 8o
    Ja, hat mich auch grad gewundert. Habs grad selber getestet. Vielleicht haben sie daran was geändert. Ich weiss auf alle Fälle, dass ich vor ein paar Jahren Probleme mit Cross-Thread-Calls hatte. Hab sogar noch die Webseite, wo ich damals die Lösung fand.

    vbcity.com/forums/t/99260.aspx

    Seitdem hab ich die Dinger nicht mehr gebraucht. Ich sehs ja selber, es geht, ohne zu meckern. Also entschuldige ich mich für die Falschaussage, ich hatte es noch so im Kopf. Oder jemand hat ne bessere Erklärung.

    Wieder zum Problem. Der Zugriff erfolgt aber dennoch parallel. Da die beiden Events quasi zur gleichen Zeit aufgerufen werden. Ich überleg noch an Deinem Problem.
    Die Unendlichkeit ist weit. Vor allem gegen Ende. ?(
    Manche Menschen sind gar nicht dumm. Sie haben nur Pech beim Denken. 8o
    :P

    Selbst wenn ich die Prüfung von "AlleFertig" in "nextSub" verschiebe, habe ich nur nochmehr Probleme.
    Der erste Aufruf wird gemacht - wunderbar. Danach stürzt Programm ab bzw. es beendet sich einfach. (Ohne Fehlermeldung. Nichtmal die Meldung von Windows "123.exe reagiert nicht mehr".)
    Kann sein, dass ich es vorher auch hatte, aber da ist mir es nicht aufgefallen.

    Edit: Der erste Aufruf wird doch nicht gemacht. Obwohl da eig. nurnoch:

    VB.NET-Quellcode

    1. Sub watcher2(ByVal sender As Object, ByVal e As System.IO.FileSystemEventArgs) Handles watcher2.Deleted
    2. 'Verarbeitung
    3. nextSub()
    4. AlleFertig = True
    5. End Sub
    steht.



    Mfg.
    SAR

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „SAR-71“ ()

    Ich hab jetzt mal versucht, Dein Problem nachzustellen. Mach nur ein Flag, das angibt, ob die Sub ausgeführt wird und umschliesse die nextSub() mit einem SyncLock. So wird sichergestellt, dass jeweils nur ein gleichzeitiger Zugriff auf den umschlossenen Code erfolgt. Teste mal.

    VB.NET-Quellcode

    1. Public Class Form1
    2. Private _file1 As String = "c:\test\1.txt"
    3. Private _file2 As String = "c:\test\2.txt"
    4. Private _subDone As Boolean = False
    5. Private _loc As New Object
    6. Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
    7. File.Delete(_file1)
    8. File.Delete(_file2)
    9. End Sub
    10. Private Sub fW1_Deleted(sender As Object, e As System.IO.FileSystemEventArgs) Handles fW1.Deleted
    11. NextSub()
    12. End Sub
    13. Private Sub fW2_Deleted(sender As Object, e As System.IO.FileSystemEventArgs) Handles fW2.Deleted
    14. NextSub()
    15. End Sub
    16. Private Sub NextSub()
    17. SyncLock _loc
    18. If Not _subDone Then
    19. _subDone = True
    20. MessageBox.Show("huhu")
    21. End If
    22. End SyncLock
    23. End Sub
    24. End Class


    Was auch noch ne Möglichkeit ist, ist dieses Gebilde hier:

    VB.NET-Quellcode

    1. Public Class Form1
    2. Private _file1 As String = "c:\test\1.txt"
    3. Private _file2 As String = "c:\test\2.txt"
    4. Private _w1Done As Boolean = False
    5. Private _w2Done As Boolean = False
    6. Private WithEvents _t As New Timer
    7. Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
    8. _t.Start()
    9. File.Delete(_file1)
    10. Threading.Thread.Sleep(1000)
    11. File.Delete(_file2)
    12. End Sub
    13. Private Sub fW1_Deleted(sender As Object, e As System.IO.FileSystemEventArgs) Handles fW1.Deleted
    14. _w1Done = True
    15. End Sub
    16. Private Sub fW2_Deleted(sender As Object, e As System.IO.FileSystemEventArgs) Handles fW2.Deleted
    17. _w2Done = True
    18. End Sub
    19. Private Sub NextSub()
    20. MessageBox.Show("huhu")
    21. End Sub
    22. Private Sub _t_Tick(sender As Object, e As System.EventArgs) Handles _t.Tick
    23. If _w1Done And _w2Done Then
    24. _t.Stop()
    25. NextSub()
    26. End If
    27. End Sub
    28. Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
    29. _t.Interval = 100
    30. End Sub
    31. End Class
    Die Unendlichkeit ist weit. Vor allem gegen Ende. ?(
    Manche Menschen sind gar nicht dumm. Sie haben nur Pech beim Denken. 8o

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

    Es sah vielsprechend aus, aber wirkt bei mir nicht.

    VB.NET-Quellcode

    1. SyncLock einObject
    2. If Not AlleFertig then
    3. AlleFertig = True
    4. Exit Sub
    5. End If
    6. End SyncLock
    7. 'Verarbeitung


    So sieht es gerade in "NextSub" aus.
    Ergebnis: Es passiert nichts und irgendwann verschwindet der Prozess aus dem TaskManager einfach (keine Fehlermeldung). Etwa 10s nachdem die FileWatcher fertig waren.

    Wenn ich noch eine MsgBox reinziehe, dann verschwindet der Prozess komischerweise nicht mehr. Aber mir die MsgBox auch nicht angezeigt.

    Edit: Vllt. sollte ich mal nebenbei erwähnen, dass die Subs alle "Shared" sind. Und somit auch die Watcher.
    Edit2: Um so ein Timer gefummle komme ich wohl nicht rum. Es sei denn, man Ereignisse auf die Veränderung von Variablen legen.



    Mfg.
    SAR

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „SAR-71“ ()

    SAR-71 schrieb:

    Vllt. sollte ich mal nebenbei erwähnen, dass die Subs alle "Shared" sind. Und somit auch die Watcher.
    Schmeiß alles Shared und Modul raus :!:
    Arbeite mit Instanzen und Events.
    Leg Dir 2 Instanzen einer Klasse mit dem Watcher an und lass sie bei Eintreffen des Ereignisses an das Hauptprogramm ein Event senden. Wenn beide Events da waren, mach weiter.
    Einen Timer brauchst Du nicht.
    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!
    So. Ich hab mal einen Test gemacht.
    Das Prinzip stimmt schon. Nur sollest Du
    1. Invoke verwenden.
    2. Eine eigene Klasse für die beiden FileSystemWatcher verwenden. Dadurch kannst Du Code aus der Hauptform auslagern.
    3. SyncLock verwenden, um doppeltes Auslösen des Events zu verhindern.

    Bei mir ist das herausgekommen:

    VB.NET-Quellcode

    1. Class TwoWatchers
    2. Public Event WatchersFinished()
    3. Dim WithEvents Watcher1 As New System.IO.FileSystemWatcher
    4. Dim WithEvents Watcher2 As New System.IO.FileSystemWatcher
    5. Dim Watcher1Handled As Boolean = False
    6. Dim Watcher2Handled As Boolean = False
    7. Dim EventRaised As Boolean = False
    8. Dim LockObject As New Object
    9. Public Sub New(Directory1 As String, Directory2 As String)
    10. 'Anpassen.
    11. 'Optional:
    12. ' Über Parameter die benötigten Werte verlangen.
    13. ' Über Parameter die FileSystemWatcher-Instanzen verlangen.
    14. Watcher1.Path = Directory1
    15. Watcher1.EnableRaisingEvents = True
    16. Watcher1.IncludeSubdirectories = False
    17. Watcher2.Path = Directory2
    18. Watcher2.EnableRaisingEvents = True
    19. Watcher2.IncludeSubdirectories = False
    20. End Sub
    21. Private Sub Watcher1Handler() Handles Watcher1.Deleted
    22. Form1.Shared_AddLog("Watcher1")
    23. Watcher1.EnableRaisingEvents = False
    24. Watcher1Handled = True
    25. Check()
    26. End Sub
    27. Private Sub Watcher2Handler() Handles Watcher2.Deleted
    28. Form1.Shared_AddLog("Watcher2")
    29. Watcher2.EnableRaisingEvents = False
    30. Watcher2Handled = True
    31. Check()
    32. End Sub
    33. Private Sub Check()
    34. Form1.Shared_AddLog("Check.VorSyncLock")
    35. SyncLock LockObject
    36. Form1.Shared_AddLog("Check.InSyncLock")
    37. If Watcher1Handled AndAlso Watcher2Handled AndAlso Not EventRaised Then
    38. 'Falls der jeweils andere Thread bereits auf den Eintritt in den SyncLock-Block wartet, würde der event zweimal ausgelöst werden.
    39. 'Durch ein Flag wird das verhindert.
    40. EventRaised = True
    41. RaiseEvent WatchersFinished()
    42. End If
    43. End SyncLock
    44. End Sub
    45. End Class


    (Kopiere den Code bitte nicht einfach, sondern versuche zu verstehen, was ich gemacht habe. Wenn Du Fragen hast, dann frag ;) )

    In der Form1 habe ich per Shared Methode -> Shared Event -> Instanzmethode das Übergeben der Werte aus der einen Klasse zur anderen geregelt. Das ist aber Nebensache.

    In der Form1 steht dann nur noch

    VB.NET-Quellcode

    1. Dim WithEvents Watcher As New TwoWatchers("C:\Dokumente und Einstellungen\...\Desktop\1\", "C:\Dokumente und Einstellungen\...\Desktop\2\")
    2. Private Sub Finished() Handles Watcher.WatchersFinished
    3. Me.Invoke(Sub() AddLog("Beide fertig"))
    4. End Sub


    Ergebnis:


    Und um die Klasse wiederverwendbar zu machen, kannst Du ihr eine Reset-Methode geben, in der Du die Flags und die EnableRisingEvents-Eigenschaften der Watcher zurücksetzt.


    Edit:

    Ich habe mal testweise beide Watcher den selben Ordner überprüfen lassen und dann eine Datei darin gelöscht.


    Wie man sieht, wird der Event nur einmal ausgelöst.

    Wenn ich das richtig im Kopf habe, müsste auch Check.VorSyncLock zweimal hintereinander stehen. Vielleicht hat der Compiler da ein bisschen herumgewerkelt.
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils

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

    Sieht für mich alles klar aus.
    Nur eine Frage bleibt mir: Warum wird in "Finished()" der "AddLog" invoket? Das müsste da doch wieder (Thread-)Safe sein? Und warum wird in der Klasse nicht mit Inovkes gearbeitet?

    Ansonsten ein geiler Code. Gleich auch ein guter Einstieg in Klassen. (Hab mich mit verschiedenen Klassen noch nicht viel beschäftigt)



    Mfg.
    SAR
    Stell Dir zwei Personen anstelle von zwei Threads vor.
    Denen gibst Du Anleitungen, was sie zu tun haben (Code).

    Die eine Person (Person 1) kümmert sich um die GUI (Das ist der "normale" Thread, den man so hat, wenn man 'ne 0815 Anwendung hat).
    Die andere Person (Person 2) ist ein FileSystemWatcher-Thread.

    Wenn jetzt (2) drauf kommt, dass die Datei gelöscht wurde, dann verwendet sie die Sprungziele, die dem Deleted-Event der FileSystemWatcher-Klasse hinterlegt sind. Das ist im Moment nur eines, nämlich die Methode "Watcher1Handler()".
    (2) macht jetzt also das, was in dieser Methode steht ( (1) macht gerade ein Nickerchen oder mäht den Rasen oder sonswas).
    Jetzt überspringen wir mal den unwichtigen Teil und gehen direkt zu Zeile 50.
    Wieder werden die Sprungziele genommen (wieder nur eines) und dort wird hingesprungen. Also macht (2) jetzt das, was in der Methode Finished() steht.

    Jetzt wichtig: Me.Invoke(...) wird von (2) aufgerufen, nicht von (1).

    Durch Me.Invoke(Sub() ...) schreibt (2) sozusagen einen Zettel mit einer Anleitung für (1) und legt ihn ins Postfach. Damit ist die Arbeit von (2) getan und sie geht wieder zurück bis in den FileSystemWatcher.
    Wenn (1) mit dem Rasenmähen oder schlafen fertig ist und in den Briefkasten sieht (also wenn der GUI-Thread alle vorigen Aufgaben abgearbeitet hat), dann sieht sie "Aha, Jemand hat mir eine Anleitung geschickt. Da steht, ich solle doch nach der Anleitung in AddLog(String) den String "Beide fertig" bearbeiten."
    Also macht das (1) auch. Sie nimmt diesen String und macht nach der Anleitung AddLog(String) weiter.



    Das ist zwar etwas dämlich geschrieben, aber ich hoffe, dass ich Dir erklären konnte, warum der Code in der Finished() Methode von einem anderen Thread ausgeführt wird.
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils
    Ähm, nein. Erst nach dem dritten Mal konnte man Ansatzweise verstehen (bzw. die Zusammenhänge erahnen) was du da geschrieben hast. :D
    Die ganze Aussage kann man reduzieren auf:" Finished" wird von einem anderem Thread behandelt.

    Wenn aber "Finished" von einem anderem Thread abgearbeitet wird - deswegen wird invoket.
    Aber warum wird in "Watcher1Handler" nicht invoket? Ist doch auch ein anderer Thread.


    Mfg.
    SAR
    Du musst ja nur einmal in den GUI-Thread wechseln.
    Wenn ich will, dass Du Deinen Hut änderst (das darf ich nicht machen), dann sage ich es Dir einmal. Nicht mehrmals.
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils
    Das erklärt meine Frage nicht wirklich.
    (Für andere Leute mag es hilfreich sein, aber für mich ist es eine Last wenn du mit irgendwelchen Vergleichen ankommst. :P)

    Edit:
    @RodFromGermany: Ich hab keine Module und Shared benutze ich zwangsweise (Sind CodeDom-Programme).
    Deswegen helfen mir die Geschichten mit Me nicht weiter - weil ich kein Me habe. :D


    Mfg.
    SAR

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „SAR-71“ ()

    SAR-71 schrieb:

    weil ich kein Me habe.
    Von jeder Klasseninstanz aus gibst Du mit Me eine Referenz auf sich selbst weiter.
    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!