By using this site, you agree to our updated Privacy Policy and our Terms of Use. Manage your Cookies Settings.
443,327 Members | 849 Online
Bytes IT Community
+ Ask a Question
Need help? Post your question and get tips & solutions from a community of 443,327 IT Pros & Developers. It's quick & easy.

VB.NET Threaded DLL

P: n/a
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

Mar 28 '06 #1
Share this Question
Share on Google+
7 Replies


P: n/a
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:
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


Mar 28 '06 #2

P: n/a
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

Mar 29 '06 #3

P: n/a
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

Thanks for your help,

Colly


Mar 29 '06 #4

P: n/a
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

Mar 29 '06 #5

P: n/a

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.

Thanks again for your help,

Colly


Mar 29 '06 #6

P: n/a
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

Mar 30 '06 #7

P: n/a

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

Mar 30 '06 #8

This discussion thread is closed

Replies have been disabled for this discussion.