FileSystemWatcher threadübergreifend

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

Es gibt 26 Antworten in diesem Thema. Der letzte Beitrag () ist von strzata.

    FileSystemWatcher threadübergreifend

    Guten Abend,
    in meinem Main Projekt habe ich eine Klasse (TimerApp.vb, kein Form, Inherits ApplicationContext) mit einem FileSystemWatcher. Wenn der feuert, soll ein Formular in einer anderen Klasse erstellt werden. Mit einem WindowsFormsTimer bekomme ich es hin, nicht aber mit dem Watcher. Ich habe es mit Threads und Delegaten versucht, aber es will mir nicht gelingen.
    Das Formular ist ein Rezeptformular (frmRezeptRot.vb) mit vielen Controls. Die müssen aus einer DataRow gefüllt und dann das Rezept angezeigt werden.

    VB.NET-Quellcode

    1. Public Class frmRezeptRot
    2. Private _timerApp As TimerApp
    3. Private _record As DataRow
    4. Sub New(timerApp As TimerApp, record As DataRow)
    5. InitializeComponent()
    6. _timerApp = timerApp
    7. _record = record
    8. PopulateForm() 'Controls befüllen
    9. Me.BringToFront()
    10. End Sub


    Im Main rufe ich im WatcherChanged die Routine CheckAndOpenForms() auf. Die sieht ungefähr so aus:

    VB.NET-Quellcode

    1. Dim f As New frmRezeptRot(Me, rw)
    2. f.Show()

    Das Rezept wird nun dargestellt, aber die Controls sind nicht gefüllt. Der Code ist natürlich viel umfangreicher. Ich habe hier nur das Kerngerüst dargestellt.
    Wie gesagt - läuft mit einem Timer problemlos. Ist da ja auch alles der gleiche Thread. Wie muss ich das jetzt mit dem Watcher machen? Wäre toll, wenn mir jemand helfen könnte. Sitze schon den ganzen Tag :cursing:
    Viele Grüße
    Norbert

    CodeTags korrigiert ~VaporiZed

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

    Ich sehe in deinem Code gar keinen FileSystemWatcher.
    Auch habe ich nicht verstanden was genau nicht funktionert. Ich denke die DataRow wird nicht im neuen Form geladen, wie du dir vorstellst, ich sehe so aber keinen Zusammenhang.
    Ansonsten wäre es noch hilfreich wenn du die funktionierende Timer Variante gegenüberstellst.
    Auf welches Dateiereignis soll denn der FileSystemWatcher (FSW) reagieren? Nur zur Klarheit: ein FSW hat nix mit Watch (= Uhr) zu tun?
    Kommt denn das Ergebnis aus dem Dateisystem an? Ist sichergestellt, dass der FSW-EventHandler reagiert? Ist der FSW scharfgeschaltet?
    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.
    Danke für euer Interesse an meinem Problem. Den gesamten Code hier zu posten sprengt den Rahmen. Nur soviel:
    Schwester am Tresen - bereitet Rezept vor. Dieses muss zur "Absegnung" zum Arzt. Beide im selben Netz und auf derselben Datenbank. Schwester klickt "Rezept zum Arzt schicken". Rezeptinhalt wird in eine Tabelle der DB geschrieben. Gleichzeitig wird auf dem Fileserver eine Datei angelegt. Arzt bekommt auf seinem Rechner das Rezept angezeigt und kann "bestätigen" oder "stornieren". Bislang lief hier ein Timer, der in der DB geprüft hat, ob ein neuer Datensatz erstellt wurde. Da der Timer auf 1 Minute eingestellt war (kürzer hat sich nicht bewährt), nahm der gesamte Vorgang mitunter zu viel Zeit in Anspruch. Deshalb jetzt der Versuch mit dem Watcher. Das Rezept beim Arzt poppt ohne Verzögerung sofort auf, wenn die Datei auf dem Server erstellt wurde. Das ist viel komfortabler, als die Sache mit dem Timer. Aber nun habe ich das Thread-Problem. Das Rezept wird beim Arzt angezeigt, aber ohne Inhalt.
    Auf welches Dateiereignis soll denn der FileSystemWatcher (FSW) reagieren?

    Wenn ein neues File in einem bestimmten Verzeichnis auf dem Server angelegt wurde.
    Ich denke die DataRow wird nicht im neuen Form geladen

    Beim Debuggen sehe ich in meiner Klasse frmRezeptRot genau, wie StepByStep die Controls beschrieben werden.
    Hier nochmal ein Auszug aus dem Code:

    VB.NET-Quellcode

    1. Public Class TimerApplication
    2. Inherits ApplicationContext
    3. Private Sub StartFileWatcher()
    4. If watcher Is Nothing Then
    5. watcher = New FileSystemWatcher
    6. watcher.Path = drive & "\temp"
    7. watcher.Filter = "asked.ref"
    8. watcher.NotifyFilter = (NotifyFilters.FileName)
    9. AddHandler watcher.Created, AddressOf WatcherChanged
    10. End If
    11. watcher.EnableRaisingEvents = True
    12. End Sub
    13. Private Sub WatcherChanged(o As Object, e As FileSystemEventArgs)
    14. watcher.EnableRaisingEvents = False
    15. CheckAndOpenForms()
    16. watcher.EnableRaisingEvents = True
    17. End Sub
    18. Private Sub CheckAndOpenForms()
    19. If Not idbs.OpenConnection(mConnector) Then
    20. MessageBox.Show("Keine Verbindung zum Server")
    21. Dispose()
    22. Environment.Exit(0)
    23. End If
    24. table = idbs.GetRecFromTable("SELECT * FROM t350", mConnector)
    25. If table Is Nothing Then
    26. Dispose()
    27. Environment.Exit(0)
    28. End If
    29. For Each rw As DataRow In table.Rows
    30. Dim record As String = CStr(rw("C3501"))
    31. If Not existingRecords.Contains(record) Then
    32. Player.Play()
    33. Select Case CStr(rw("C3502"))
    34. Case "1"
    35. Dim f As New frmRezeptRot(Me, rw)
    36. f.Show()
    37. Case "2"
    38. Dim f As New frmRezeptBlau(Me, rw)
    39. ...
    40. End Class
    41. Public Class frmRezeptRot
    42. Private _timerApplication As TimerApplication
    43. Private _record As DataRow
    44. Sub New(timerApplication As TimerApplication, record As DataRow)
    45. InitializeComponent()
    46. _timerApplication = timerApplication
    47. _record = record
    48. PopulateForm()
    49. Me.BringToFront()
    50. End Sub
    51. Private Sub PopulateForm()
    52. Me.TopMost = True
    53. Me.Text = CStr(_record("C3503"))
    54. Me.BackgroundImage = Image.FromFile("Images\rRezept.png")
    55. Dim gesamt As String = CStr(_record("C3504"))
    56. Dim items() As String = gesamt.Split("»"c)
    57. For Each item As String In gesamt.Split("»"c)
    58. SetValue(item.Split("«"c)(0), item.Split("«"c)(1))
    59. Next
    60. End Sub
    61. Private Sub SetValue(ByVal FieldName As String, ByVal FieldValue As String)
    62. For Each c As Control In Me.Controls
    63. If c.Name = FieldName Then
    64. c.Text = FieldValue
    65. Exit For
    66. End If
    67. Next
    68. End Sub
    69. Private Sub btnConfirm_Click(sender As Object, e As EventArgs) Handles btnConfirm.Click
    70. _timerApplication.Delete(CStr(_record("C3501")))
    71. Me.Close()
    72. End Sub
    73. Private Sub btnStorno_Click(sender As Object, e As EventArgs) Handles btnStorno.Click
    74. _timerApplication.RemoveHash(CStr(_record("C3501")))
    75. Me.Close()
    76. End Sub
    77. End Class


    Bei Verwendung des Timers funktioniert alles bestens. Die Klasse frmRezeptRot (oben vollständig dargestellt) bleibt so wie sie ist. Lediglich in der Klasse, wo jetzt der Watcher liegt, gab es die Sub

    VB.NET-Quellcode

    1. Private Sub timer_Tick(sender As Object, e As EventArgs)
    2. timer.Stop()
    3. CheckAndOpenForms()
    4. timer.Interval = 30000
    5. timer.Start()
    6. End Sub


    CodeTags korrigiert; bitte zukünftig darauf achten, das richtige CodeHighlighting zu verwenden ~VaporiZed

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

    Vielleicht musst Du einfach nur eine kurze Pause programmieren, bevor Du die Datei schreibst.
    Denn möglicherweise ist die Datenbank noch nicht mit schreiben fertig und Du bekommst deswegen eine leere Anzeige.
    Liebe Grüße
    Roland Berghöfer

    Meine aktuellen und kostenlos verwendbaren Tools (mit VB.NET erstellt): freeremarkabletools.com | priconman.com | SimpleCalendar | AudibleTouch | BOComponent.com | bonit.at
    Vielen Dank ihr lieben Helferlein! Ich verlier noch die letzten Haare ?(
    Vielleicht musst Du einfach nur eine kurze Pause programmieren, bevor Du die Datei schreibst.

    Meinst Du die Datei auf dem Server? Die ist geschrieben, sonst würde der watcher nicht anspringen. Und die Row aus der Datenbank ist auch vollständig geholt. Ich seh die Werte ja beim Debuggen.

    Es wird vermutlich das Created-Event mehrmals geworfen. Das solltest du auch berücksichtigen...

    Wo wird denn ein Created-Event gefeuert? Im frmRezeptRot?

    Ich denke, es muss ein Invoke rein, da der Watcher in einem anderen Thread läuft. Oder ein InvokeRequired? Oder ein Delegate? Mist, wenn man so ein Dummy ist wie ich und von nichts keine Ahnung hat. Await, Action und alles, was es vor 30 Jahren noch nicht gab, hab ich einfach verpasst. Aber Threading müsste ich eigentlich können, kanns aber nicht.
    Ich hatte ein ähnliches Problem: Dokument in VB erstellen, im Netzwerk als PDF speichern, anzeigen lassen. Gab immer wieder Probleme, weil die Datei (angeblich) nicht fertig war. Hab dann einen Delay reingemacht, dann ging es (bisher). Probier mal:

    VB.NET-Quellcode

    1. Private Async Sub ÖffneDatei(Dateipfad As String)
    2. For i = 1 To 10
    3. If IO.File.Exists(Dateipfad) Then Diagnostics.Process.Start(Dateipfad): Return 'oder wasauchimmer
    4. Await Threading.Tasks.Task.Delay(1000) '= 1 Sekunde warten
    5. Next
    6. MessageBox.Show("Die Datei kann auch nach mehreren Versuchen nicht angezeigt werden.")
    7. End Sub
    Für den Fall, dass es nicht ging, hab ich nur die Datei im Dateiexplorer anzeigen lassen.
    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.
    Danke. Betrifft mein Problem leider nicht. Die auf den Server gelegte Datei dient nur als Trigger für den Watcher. Sie hat keinen Inhalt und muss auch ncht angezeigt werden. Wenn der Watcher reagiert hat, wird sie auch gleich wieder gelöscht. Die Daten, die angezeigt werden sollen stehen in einer DB.
    Durchaus möglich, dass es tatsächlich an der Nebenläufigkeit liegt. Jetzt wird's insofern interessant, weil Du im (vermuteten) Nebenthread des FSW auch die Forms erstellst. Da müsstest Du dafür sorgen, dass die im GUI-Thread erstellt werden. Gibt bestimmt schönere Methoden, aber übergib mal an die TimerApplication-Klasse das aufrufende Form, speicher es in einer Variable und nutze es, um die SubForms im GUI-Thread mittels BeginInvoke aufzurufen.

    VB.NET-Quellcode

    1. Public Class TimerApplication : Inherits ApplicationContext
    2. Private ParentForm As Form
    3. Sub RememberParentForm(Form As Form)
    4. ParentForm = Form
    5. End Sub
    6. '…
    7. Private Sub CheckAndOpenForms()
    8. '…
    9. For Each rw As DataRow In table.Rows
    10. Dim record As String = CStr(rw("C3501"))
    11. If Not existingRecords.Contains(record) Then
    12. Player.Play()
    13. Select Case CStr(rw("C3502"))
    14. Case "1"
    15. ParentForm.BeginInvoke(Sub()
    16. Dim f As New frmRezeptRot(Me, rw)
    17. f.Show()
    18. End Sub)

    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.

    VaporiZed schrieb:

    Durchaus möglich, dass es tatsächlich an der Nebenläufigkeit liegt
    Sollte dabei nicht eine Exception geworfen werden?

    Haudruferzappeltnoch schrieb:

    Wenn mehrere Events gefeuert werden würden, dann würden ja auch mehrere Forms öffnen.
    In seinem Fall glaub ich nicht, weil er dort die folgenden Events abwürgt bevor die Form erstellt wird...

    Nee, eben nicht. Forms können nebenläufig erzeugt werden. Nur die threadübergreifende Änderung von Controls oder angezeigter Daten führt zu der Fehlermeldung. Das Problem hatte Bartosz schon mal. Mal sehen, ob ich es finde …
    Hui, Gedächtnis funktioniert, obwohl der Thread fast 3 Jahre alt ist: In einer asynchronen Sub wird eine neue Instanz eines Dialogs erstellt. Threadübergeifender Vorgang trotz Gebrauch von Me.Invoke
    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.
    Ihr seid toll!!!
    Bevor ich jetzt alles, was ihr geschrieben habt, durcharbeite, stelle ich euch eine abgespeckte Variante als sln hier rein. Ist nur 25 MB groß. Braucht nix weiter als den Code.
    Testen mit Timer ist voreingestellt. Für den Ablauf mit dem Watcher habe ich Vermerke gemacht, was ein- bzw. auskommentiert werden muss (ist nur an 5 Stellen nötig: Timer aus - Watcher ein).
    In Main-->Bin liegt eine leere Datei namens asked.ref. Diese einfach nach Main-->Bin-->Debug kopieren und der Watcher bzw. Timer springt an. Wenn euch die Datei verloren geht, einfach eine neue leere mit diesem Namen erstellen.
    Im Watcher-Modus bleibt das Programm hängen!
    Dateien

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

    !Workaround!:

    VB.NET-Quellcode

    1. Public Class TimerApplication
    2. Inherits ApplicationContext
    3. '…
    4. Private DummyForm As New Form
    5. Public Sub New()
    6. '…
    7. DummyForm.Show()
    8. DummyForm.Hide()
    9. For Each row As DataRow In dt.Rows
    10. Dim f As New frmRezeptRot(Me, row)
    11. DummyForm.BeginInvoke(Sub() f.Show())
    12. Next

    Somit bekommst Du ein Form im GUI-Thread, mit dem Du die anderen wie gewünscht erstellen und befüllen kannst.

    Deinen Anhang muss ich nachbearbeiten, ausführbare Dateien sind laut Boardregeln nicht erlaubt.
    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.
    Irgendwie war ich plötzlich ausgesperrt. Scheinbar ist mein letztes Posting hängen geblieben.
    Ich hatte geschrieben, dass es nicht funktioniert. Aber dann ging mir ein Licht auf. Das Fensterhandle wird erst durch

    Quellcode

    1. DummyForm.Show()
    2. DummyForm.Hide()

    erstellt. Ich dachte, dieser Code sei nur zum Rumprobieren und hatte ihn weggelassen. Du kennst ja tolle Tricks!
    Jetzt klappt es problemlos. Ich bin voll der Dankbarkeit, auch an alle anderen, die sich eine Rübe gemacht haben. Ihr seid umwerfend!
    Viele Grüße
    Norbert

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

    Was die funktionsuntüchtige Variante angeht:
    Ich stelle fest, wenn der Watcher (also nebenläufig), die Forms aufruft, dann scheinen die nie vollständig geladen zu werden, die Erreichen zwar das Load-Event, aber nicht das Shown-Event
    Mysteriös, gibts da noch Events dazwischen?
    Beim Timer kriegt man das Shown-Event ganz normal.

    Und aus demselben Grund wird der eingeschriebene Text nicht in die Controls gemalt. Denn das passiert beim Timer auch erst wenn die Forms "zuende geladen" sind, was auch immer das in diesem Context bedeutet.

    Haudruferzappeltnoch schrieb:

    Ich stelle fest, wenn der Watcher (also nebenläufig), die Forms aufruft
    ich tät Nebenläufigkeit immer zu vermeiden suchen.
    gib ihm einen SynchronisizingObject - meinetwegen ein unsichtbares Dummi-Form.
    Und gut.

    Besser: Pack die FSW-Component aufs MainForm.
    Dannis sofort alles gut.
    Dieses Dim FSW as New FSW ist eine Unsitte, die Nebenläufigkeit produziert, die meist niemand braucht.

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