Threading(?) & FileSystemWatcher

H

hooksie2

Hi,

I am trying to use FileSystemWatcher 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.ArgumentNullException'
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 FileSystemWatcher
Private frm As frmMain

Public Sub StartFileWatch(ByVal frmLogForm As frmMain)

mfswWatchFolder = New FileSystemWatcher
mfswWatchFolder.SynchronizingObject = frmLogForm.lbxRunLog
'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:\Documents 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.NotifyFilters.DirectoryName
mfswWatchFolder.NotifyFilter = mfswWatchFolder.NotifyFilter Or
IO.NotifyFilters.FileName
mfswWatchFolder.NotifyFilter = mfswWatchFolder.NotifyFilter Or
IO.NotifyFilters.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.EnableRaisingEvents = 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
 
R

Robin Tucker

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
 
H

hooksie2

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(astrLines)

The form code looks like:

Public Sub LogMsg(ByVal astrLogText() As String)
lbxRunLog.BeginUpdate()
lbxRunLog.Items.AddRange(astrLogText)
'Make sure the bottom entry is visible
lbxRunLog.TopIndex = lbxRunLog.Items.Count - 1
lbxRunLog.EndUpdate()
Me.lbxRunLog.Refresh()
End Sub

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

Andrew
 
R

Robin Tucker

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(ByVal 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.BeginUpdate()
lbxRunLog.Items.AddRange(astrLogText)

' Make sure the bottom entry is visible

lbxRunLog.TopIndex = lbxRunLog.Items.Count - 1
lbxRunLog.EndUpdate()

Me.lbxRunLog.Invalidate ()

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
 
H

hooksie2

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
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top