Home All Groups Group Topic Archive Search About
Author
28 Mar 2006 3:30 PM
CollyMitch
Hi,

I've been trying to create a multithreaded dll to be used for simple
error logging to a file. The DLL will need to be imported into a .NET
framework based application (Wonderware). I am having a problem
importing into wonderware, getting the error message "...Denotes a
field where a class was expected".

I carry out the threading using the Mutex object, can this be used in a
DLL class? It also imports System.IO and System.Threading - could this
be a factor?

The DLL seems to work when I use a dummy VB application to call it -
although it does get the logging mixed up (i.e. time not in sequence)
but it does prevent any file locking errors.

The structure of the class is as follows:

Public Class logMessageToFile
   Private Shared FileLockMutex As New Mutex
   Private Class loggerClass
     Public strMessageText As String
     *Some more public vars declared here*
     Public Sub LogMessage()
        *   use mutex here*
        *   log messages to file*
        *   release mutex *
     End sub
  End Class
  Public Shared Sub writeToLog(ByVal MsgText As String, *other params*)
     Dim cLog As New loggerClass
     Dim thread1 As System.Threading.Thread
     thread1 = New Threading.Thread(AddressOf cLog.LogMessage)
     cLog.strMessageText = MsgText
     * store other params*
     thread1.Start()
  End sub
End class

As you can see I had difficulty ensuring that the variables remained
private to each thread so I [thought] had to use a class to store them.
If anyone can see any issues as to why this wouldn't work then let me
know and I will resolve them and try again in wonderware.

Thanks in advance,

Colly Mitchell

Author
28 Mar 2006 7:49 PM
Brian Gideon
Colly,

It doesn't sound like the error message has anything to do with
threading to me.  When do you get the message?  Is it when you
reference the assembly from Wonderware?  Is it in code that you've
written inside Wonderware?

Also, I don't think the use of threads is appropriate here.  You're
creating a new thread on each and every log message.  That's going to
be slow.  You probably don't need to use threads at all.  Writing to
the file should be very quick.  If you still think you need
asynchronous processing of log messages then I recommend using a single
thread.  That thread would more or less sit in an infinite loop waiting
for log messages to appear in a queue.  It would dequeue them one at a
time and stick them in a file serially.  Your writeToLog method would
simply enqueue the message.

Another thing, if you need to synchronize access to a shared resource
the SyncLock keyword or Monitor class is usually better than using a
Mutex.  The Mutex is intended for use cases involving the
synchronization of multiple processes.

You can't make variables or classes private to thread.  It just doesn't
work that way.  You can simulate it by using thread local storage
though.

Brian

CollyMitch wrote:
Show quoteHide quote
> Hi,
>
> I've been trying to create a multithreaded dll to be used for simple
> error logging to a file. The DLL will need to be imported into a .NET
> framework based application (Wonderware). I am having a problem
> importing into wonderware, getting the error message "...Denotes a
> field where a class was expected".
>
> I carry out the threading using the Mutex object, can this be used in a
> DLL class? It also imports System.IO and System.Threading - could this
> be a factor?
>
> The DLL seems to work when I use a dummy VB application to call it -
> although it does get the logging mixed up (i.e. time not in sequence)
> but it does prevent any file locking errors.
>
> The structure of the class is as follows:
>
> Public Class logMessageToFile
>    Private Shared FileLockMutex As New Mutex
>    Private Class loggerClass
>      Public strMessageText As String
>      *Some more public vars declared here*
>      Public Sub LogMessage()
>         *   use mutex here*
>         *   log messages to file*
>         *   release mutex *
>      End sub
>   End Class
>   Public Shared Sub writeToLog(ByVal MsgText As String, *other params*)
>      Dim cLog As New loggerClass
>      Dim thread1 As System.Threading.Thread
>      thread1 = New Threading.Thread(AddressOf cLog.LogMessage)
>      cLog.strMessageText = MsgText
>      * store other params*
>      thread1.Start()
>   End sub
> End class
>
> As you can see I had difficulty ensuring that the variables remained
> private to each thread so I [thought] had to use a class to store them.
> If anyone can see any issues as to why this wouldn't work then let me
> know and I will resolve them and try again in wonderware.
>
> Thanks in advance,
>
> Colly Mitchell
Author
29 Mar 2006 2:16 PM
CollyMitch
Brian,

Firstly thank-you for your feedback.

I get the message when it tries to compile.

The reason I had used a threaded solution was because the process could
only take a maximum of 500msec and a slow logging process couldn't be
allowed to affect that. I had thought that i would call the logger and
let it run in it's own thread, allowing execution to continue in the
main application. I will probably use a database based error log in
future if I got this to work.

I like the Local thread storage technique, I knew there had to be a
better way to avoid each variable being overwritten by other thread
calls.

Anyway, I agree that using a queue to log messages would be a much more
efficient solution, as there may be many attempts to log messages
within the 500msec window.

I presume then that I would call this logger at the beginning of the
process and it would be constantly running but as a separate thread,
reading from the queue and writing to a file? I can pass a user defined
type to a queue? Would it be best to stop the logger thread when the
queue is empty and call it again when I add to the queue (if there is
not a thread running already)?

Thanks for your help,

Colly
Author
29 Mar 2006 3:11 PM
Brian Gideon
CollyMitch wrote:
> Brian,
>
> Firstly thank-you for your feedback.
>
> I get the message when it tries to compile.
>

It sounds like a syntax error to me.  Fortunately, if you look hard
enough you'll eventually find it.

> The reason I had used a threaded solution was because the process could
> only take a maximum of 500msec and a slow logging process couldn't be
> allowed to affect that. I had thought that i would call the logger and
> let it run in it's own thread, allowing execution to continue in the
> main application. I will probably use a database based error log in
> future if I got this to work.
>
> I like the Local thread storage technique, I knew there had to be a
> better way to avoid each variable being overwritten by other thread
> calls.
>

I don't think you need TLS for your particular situation.  It's rarely
used anyway.

> Anyway, I agree that using a queue to log messages would be a much more
> efficient solution, as there may be many attempts to log messages
> within the 500msec window.
>

Yes.  A single worker thread approach is definitely the way to go here.

> I presume then that I would call this logger at the beginning of the
> process and it would be constantly running but as a separate thread,
> reading from the queue and writing to a file? I can pass a user defined
> type to a queue? Would it be best to stop the logger thread when the
> queue is empty and call it again when I add to the queue (if there is
> not a thread running already)?

You can either start the logger at the beginning of the process or you
could use a lazy initialization technique.  It doesn't really matter.
Yes, the logger thread, once started, would run all the time.  I
wouldn't stop it when the queue empties because in most cases the queue
would empty before the next log message was queued.  Here's some code
to get you started.  My VB is a little rusty since I'm a C# guy.

Public Class Logger

  ' You'll need to write your own BlockingQueue class.
  Private m_Queue As BlockingQueue = New BlockingQueue
  Private m_Thread As Thread = Nothing

  Public Sub Logger()

    m_Thread = New Thread(AddressOf LoggerThread)
    m_Thread.IsBackground = True
    m_Thread.Start()

  End Sub

  Public Sub WriteToLog(ByVal message As String)

    ' Put the message in the queue.  This will wake the logger thread.
    m_Queue.Enqueue(message)

  End Sub

  Private Sub LoggerThread()

    Do While True

      ' The line will block until an item appears in the queue.
      Dim message As String = DirectCast(m_Queue.DequeueWait(), String)

      WriteMessageToFile(message)

    Loop

  End Sub

End Class

The only thing I haven't provided you is the BlockingQueue.  The
blocking queue would implement the standard producer-consumer pattern
and provide a DequeueWait method that blocks if the queue is empty.  I
posted an example implementation in C# at...

<http://www.gotdotnet.com/Community/UserSamples/Details.aspx?SampleGuid=BD9982D6-B14D-402B-B855-5D74D61ABC94>

Brian

Show quoteHide quote
>
> Thanks for your help,
>
> Colly
Author
29 Mar 2006 3:55 PM
CollyMitch
Brian,

That's great thanks. I was going to ask how will I be able to add to
the queue when the thread is removing from the queue at the same time.
I would use the SyncLock method to lock the queue when adding and
removing elements? The wait handle you use - this waits until the queue
is unlocked and then adds/dequeues, this would mean that as soon as the
queue was read the wait handle is reset to allow adding?

'Lock the queue
SyncLock myQueue.SyncRoot
    msg = myQueue.Dequeue
End SyncLock 'now unlocked
WriteMessageToFile(msg)

This locks the queue before Dequeuing > what happens when I try to add
an element to the queue - i would need to use the same synclock. You
use a wait handle to wait for it to be unlocked?

>> _WaitHandle.WaitOne();

Thanks again for your help,

Colly
Author
29 Mar 2006 5:53 PM
Brian Gideon
CollyMitch wrote:
> Brian,
>
> That's great thanks. I was going to ask how will I be able to add to
> the queue when the thread is removing from the queue at the same time.
> I would use the SyncLock method to lock the queue when adding and
> removing elements? The wait handle you use - this waits until the queue
> is unlocked and then adds/dequeues, this would mean that as soon as the
> queue was read the wait handle is reset to allow adding?

The WaitHandle is used to make the Dequeue method block if nothing is
in the queue.  The Enqueue method will signal the wait handle and the
Dequeue method will wake up and try to dequeue an item before another
thread does.  If the queue is empty when the thread wakes (because
another thread woke first) then it will loop back around to the
WaitHandle.WaitOne() method again and sleep some more.

>
> 'Lock the queue
> SyncLock myQueue.SyncRoot
>     msg = myQueue.Dequeue
> End SyncLock 'now unlocked
> WriteMessageToFile(msg)
>

You should make your queue class thread-safe.  That way you won't need
to use SyncLock inside the Logger class.  I recommend taking the
BlockingQueue class I have and just port it to VB.  The synchronization
is a bit tricky and it would save you a lot of time.

> This locks the queue before Dequeuing > what happens when I try to add
> an element to the queue - i would need to use the same synclock. You
> use a wait handle to wait for it to be unlocked?
>
> >> _WaitHandle.WaitOne();

Again.  The WaitHandle is used to make to the Dequeue method block if
nothing is in the queue.  It has nothing to do with locking the queue.
That's what SyncLock (or lock in C#) do.

Show quoteHide quote
>
> Thanks again for your help,
>
> Colly
Author
30 Mar 2006 4:02 PM
CollyMitch
Brian,

I've got it working really well, really fast and it works in perfect
sequence. I've used a synchronised queue and the AutoResetEvent. I
didn't port you blocking class but did learn a lot from it.

I've declared the thread as a background thread, if I didn't do this -
would it continue to run in the infinite loop even when the calling
application ended?

Thanks again for your all help,

Colly
Author
30 Mar 2006 10:17 PM
Brian Gideon
CollyMitch wrote:
> Brian,
>
> I've got it working really well, really fast and it works in perfect
> sequence. I've used a synchronised queue and the AutoResetEvent. I
> didn't port you blocking class but did learn a lot from it.
>

AutoResetEvent is fine.  Though, the semantics of the queue may be
slightly different than my approach which used ManualResetEvent.  An
even better approach is to use Monitor.PulseAll and Monitor.Wait.  The
following article demonstrates how this is done.

<http://www.yoda.arachsys.com/csharp/threads/deadlocks.shtml>

> I've declared the thread as a background thread, if I didn't do this -
> would it continue to run in the infinite loop even when the calling
> application ended?
>

Yep.  That's pretty much right.  If you want to shutdown the thread
gracefully you'll have to modify the Logger class a bit.  Maybe
something like the following.

Public Class Logger

  ' You'll need to write your own BlockingQueue class.
  Private m_Queue As BlockingQueue = New BlockingQueue
  Private m_Thread As Thread = Nothing
  Private m_Shutdown as ManualResetEvent = New ManualResetEvent(False)

  Public Sub Logger()

    m_Thread = New Thread(AddressOf LoggerThread)
    m_Thread.IsBackground = True
    m_Thread.Start()

  End Sub

  Public Sub Stop()

    m_Shutdown.Set()
    m_Thread.Join()

  End Sub

  Public Sub WriteToLog(ByVal message As String)

    ' Put the message in the queue.  This will wake the logger thread.
    m_Queue.Enqueue(message)

  End Sub

  Private Sub LoggerThread()

    Dim keepGoing As Boolean = True

    Do While keepGoing

      Dim handles(2) as WaitHandle
      handles(0) = m_Shutdown
      handles(1) = m_Queue.WaitHandle

      Dim index as Integer = WaitHandle.WaitAny(handles)

      If index = 0 Then

        keepGoing = False

      Else

        Dim message As String = DirectCast(m_Queue.Dequeue(), String)

        WriteMessageToFile(message)

      End If

    Loop

  End Sub

End Class