Threading(?) & FileSystemWatcher

Collapse
This topic is closed.
X
X
 
  • Time
  • Show
Clear All
new posts
  • hooksie2@hotmail.com

    #1

    Threading(?) & FileSystemWatcher

    Hi,

    I am trying to use FileSystemWatch er to watch a log file which is
    written to via a 3rd party app and display the log in listbox on a
    form. The FileSystemWatch seems to work okay but I get the following
    error when I try to write to the listbox:

    "A first chance exception of type 'System.Argumen tNullException'
    occurred in System.Windows. Forms.dll"
    >From my search so far it seems this may be to do with the watcher
    being in a different thread but to be honest I haven't really figured
    the ins and outs of threading. This was meant to be a nice simple
    project to learn from so I'd really appreciate any tips.

    My form has a run button which calls the following routine (in a
    separate module):

    Private mstrPathToWatch As String
    Private mstrFileToWatch As String
    Private mfswWatchFolder As FileSystemWatch er
    Private frm As frmMain

    Public Sub StartFileWatch( ByVal frmLogForm As frmMain)

    mfswWatchFolder = New FileSystemWatch er
    mfswWatchFolder .SynchronizingO bject = frmLogForm.lbxR unLog
    'this was just a guess but I don't understand it and it
    doesn't seem to help

    'Create module level reference to form so can access it from
    changed event
    frm = frmLogForm

    'Specify the path we want to watch
    mstrPathToWatch = "C:\Documen ts and Settings\Andrew \My
    Documents\TEMP"
    mstrFileToWatch = "demo.txt"
    mfswWatchFolder .Path = mstrPathToWatch
    mfswWatchFolder .Filter = mstrFileToWatch

    'Add a list of Filter we want to specify
    mfswWatchFolder .NotifyFilter = IO.NotifyFilter s.DirectoryName
    mfswWatchFolder .NotifyFilter = mfswWatchFolder .NotifyFilter Or
    IO.NotifyFilter s.FileName
    mfswWatchFolder .NotifyFilter = mfswWatchFolder .NotifyFilter Or
    IO.NotifyFilter s.Attributes

    'Add a handler for each event (in this case just file change)
    AddHandler mfswWatchFolder .Changed, AddressOf FileHasChanged

    'Set this property to true to start watching
    mfswWatchFolder .EnableRaisingE vents = True

    End Sub

    The 'FileHasChanged ' event created above reads the log file
    (successfully) and then calls a sub back on the form which writes the
    array to the listbox.

    Thanks a lot!

    Andrew

  • Robin Tucker

    #2
    Re: Threading(?) & FileSystemWatch er

    Hi hooksie,


    I think perhaps the most important piece of code you didn't post there was
    how you are writing to the list on the form from the thread and what
    parameters you are sending through. You should ideally be "invoking" any
    methods on the form, rather than just calling sub-routines on it from your
    thread.


    Robin


    Comment

    • hooksie2@hotmail.com

      #3
      Re: Threading(?) & FileSystemWatch er

      Thanks for the reply.

      I write each line of the log to an array and then pass to the sub
      which is in the form as follows:

      frm.LogMsg(astr Lines)

      The form code looks like:

      Public Sub LogMsg(ByVal astrLogText() As String)
      lbxRunLog.Begin Update()
      lbxRunLog.Items .AddRange(astrL ogText)
      'Make sure the bottom entry is visible
      lbxRunLog.TopIn dex = lbxRunLog.Items .Count - 1
      lbxRunLog.EndUp date()
      Me.lbxRunLog.Re fresh()
      End Sub

      I'm interested to hear what you mean by "invoking" - thanks for taking
      the time to look at this.

      Andrew



      On May 2, 10:39 pm, "Robin Tucker" <rtgro...@hotma il.co.ukwrote:
      Hi hooksie,
      >
      I think perhaps the most important piece of code you didn't post there was
      how you are writing to the list on the form from the thread and what
      parameters you are sending through. You should ideally be "invoking" any
      methods on the form, rather than just calling sub-routines on it from your
      thread.
      >
      Robin

      Comment

      • Robin Tucker

        #4
        Re: Threading(?) &amp; FileSystemWatch er


        Hi,

        Looking at your code, I have to assume astrLogText = nothing when it enters
        the procedure in order to throw that exception, so put a break-point and
        check its value before AddRange. Secondly, accessing and manipulating
        windows controls from a thread other than the main process thread is fraught
        with problems. Because you don't know what the main process thread is doing
        at the time, its possible to get race, lock and other such synchronisation
        errors. "Invoke" (BeginInvoke, EndInvoke, Invoke) allow you to call a
        method on a windows form or control from a thread other than the thread they
        were created on. I think under the hood the CLR uses the controls message
        pump to ensure the methods are called in an orderly manner. Use Google to
        find out more or look in the MSDN literature. Your keywords are:
        Invoke+VB.NET :).

        There are many examples around of using Invoke. My preference is to create
        a method on the main form, in this case called LogMsg and then invoke that
        method from your thread using a delegate. The invoke procedure would look
        something like this:


        ' Declare a delegate

        Private Delegate Sub _LogMsgDelegate (astrLogText() as String)



        ' Method to invoke the LogMsg method on the main form.

        Public Sub InvokeLogMsg(By Val astrLogText() as String)

        Dim Parameters(0) As Object

        Parameters(0) = astrLogText

        ' If the invoke fails, it's usually because the form has been
        ' destroyed. The window handle of the form or control must
        ' be valid for the invocation to succeed. An unhandled exception
        ' on a form or in a control will cause .NET to dispose of it,
        ' resulting in any running thread failing when it invokes.

        Try

        frm.Invoke(New LogMsgDelegate( AddressOf frm.LogMsg), Parameters)

        Catch ex As Exception

        ...

        End Try

        End Sub



        and on your form......



        Public Sub LogMsg(ByVal astrLogText() As String)

        lbxRunLog.Begin Update()
        lbxRunLog.Items .AddRange(astrL ogText)

        ' Make sure the bottom entry is visible

        lbxRunLog.TopIn dex = lbxRunLog.Items .Count - 1
        lbxRunLog.EndUp date()

        Me.lbxRunLog.In validate ()

        End Sub



        Sorry I didn't test it, but cut and messed around with some code from one of
        my projects. Hope this is useful.

        Robin



        I write each line of the log to an array and then pass to the sub
        which is in the form as follows:
        >
        frm.LogMsg(astr Lines)
        >
        The form code looks like:
        >
        Public Sub LogMsg(ByVal astrLogText() As String)
        lbxRunLog.Begin Update()
        lbxRunLog.Items .AddRange(astrL ogText)
        'Make sure the bottom entry is visible
        lbxRunLog.TopIn dex = lbxRunLog.Items .Count - 1
        lbxRunLog.EndUp date()
        Me.lbxRunLog.Re fresh()
        End Sub
        >
        I'm interested to hear what you mean by "invoking" - thanks for taking
        the time to look at this.
        >
        Andrew
        >
        >
        >
        On May 2, 10:39 pm, "Robin Tucker" <rtgro...@hotma il.co.ukwrote:
        >Hi hooksie,
        >>
        >I think perhaps the most important piece of code you didn't post there
        >was
        >how you are writing to the list on the form from the thread and what
        >parameters you are sending through. You should ideally be "invoking" any
        >methods on the form, rather than just calling sub-routines on it from
        >your
        >thread.
        >>
        >Robin
        >
        >

        Comment

        • hooksie2@hotmail.com

          #5
          Re: Threading(?) &amp; FileSystemWatch er

          You solved it! My apparent problem turned out to be something simpler
          than I had assumed. The last value in my array was nothing and this
          was actually what was causing the error.

          I'm still struggling with the concepts around Invoke (web resources so
          far a bit over my head) but I'll keep trying and have implemented your
          suggestion anyway - at a high level I can understand how this could
          cause a problem otherwise.

          Thanks for your help.

          Andrew



          On May 3, 12:24 am, "Robin Tucker" <rtgro...@remov ehotmail.comwro te:
          Hi,
          >
          Looking at your code, I have to assume astrLogText = nothing when it enters
          the procedure in order to throw that exception, so put a break-point and
          check its value before AddRange. Secondly, accessing and manipulating
          windows controls from a thread other than the main process thread is fraught
          with problems. Because you don't know what the main process thread is doing
          at the time, its possible to get race, lock and other such synchronisation
          errors. "Invoke" (BeginInvoke, EndInvoke, Invoke) allow you to call a
          method on a windows form or control from a thread other than the thread they
          were created on. I think under the hood the CLR uses the controls message
          pump to ensure the methods are called in an orderly manner. Use Google to
          find out more or look in the MSDN literature. Your keywords are:
          Invoke+VB.NET :).
          >
          There are many examples around of using Invoke. My preference is to create
          a method on the main form, in this case called LogMsg and then invoke that
          method from your thread using a delegate. The invoke procedure would look
          something like this:
          >
          ' Declare a delegate
          >
          Private Delegate Sub _LogMsgDelegate (astrLogText() as String)
          >
          ' Method to invoke the LogMsg method on the main form.
          >
          Public Sub InvokeLogMsg(By Val astrLogText() as String)
          >
          Dim Parameters(0) As Object
          >
          Parameters(0) = astrLogText
          >
          ' If the invoke fails, it's usually because the form has been
          ' destroyed. The window handle of the form or control must
          ' be valid for the invocation to succeed. An unhandled exception
          ' on a form or in a control will cause .NET to dispose of it,
          ' resulting in any running thread failing when it invokes.
          >
          Try
          >
          frm.Invoke(New LogMsgDelegate( AddressOf frm.LogMsg), Parameters)
          >
          Catch ex As Exception
          >
          ...
          >
          End Try
          >
          End Sub
          >
          and on your form......
          >
          Public Sub LogMsg(ByVal astrLogText() As String)
          >
          lbxRunLog.Begin Update()
          lbxRunLog.Items .AddRange(astrL ogText)
          >
          ' Make sure the bottom entry is visible
          >
          lbxRunLog.TopIn dex = lbxRunLog.Items .Count - 1
          lbxRunLog.EndUp date()
          >
          Me.lbxRunLog.In validate ()
          >
          End Sub
          >
          Sorry I didn't test it, but cut and messed around with some code from one of
          my projects. Hope this is useful.
          >
          Robin
          >
          >
          >
          I write each line of the log to an array and then pass to the sub
          which is in the form as follows:
          >
          frm.LogMsg(astr Lines)
          >
          The form code looks like:
          >
          Public Sub LogMsg(ByVal astrLogText() As String)
          lbxRunLog.Begin Update()
          lbxRunLog.Items .AddRange(astrL ogText)
          'Make sure the bottom entry is visible
          lbxRunLog.TopIn dex = lbxRunLog.Items .Count - 1
          lbxRunLog.EndUp date()
          Me.lbxRunLog.Re fresh()
          End Sub
          >
          I'm interested to hear what you mean by "invoking" - thanks for taking
          the time to look at this.
          >
          Andrew
          >
          On May 2, 10:39 pm, "Robin Tucker" <rtgro...@hotma il.co.ukwrote:
          Hi hooksie,
          >
          I think perhaps the most important piece of code you didn't post there
          was
          how you are writing to the list on the form from the thread and what
          parameters you are sending through. You should ideally be "invoking" any
          methods on the form, rather than just calling sub-routines on it from
          your
          thread.
          >
          Robin- Hide quoted text -
          >
          - Show quoted text -

          Comment

          Working...