ARTICLE
Messaging between Threads using Message Loop
Tags: .NET, Callback, Delegates, DispatchClosing, DispatchMessage, Dispose, GetCommandLineArgs, GUI, Interface, Mesage Loop, Message, Messages in VB.NET., Monitor.Wait, Montior, Mu;ti-Threading, Multi-Threaded Application, System.Drawing, System.Environment, System.Threadin, VB.NET
MessageLoopLib is a stripped down version of a complete, threading communication subsystem I’ve written. This implementation is a single thread created in the GUI constructor. I’ve dropped all thread management and have had to change some of the message code to accommodate this.
Download
Files:
Introduction
MessageLoopLib is a stripped down version of a complete, threading communication subsystem I've written. This implementation is a single thread created in the GUI constructor. I've dropped all thread management and have had to change some of the message code to accommodate this.

Usage:
The "Start Thread" button is the only control enabled at startup. Clicking it starts a thread and enables the other controls, while disabling itself.
Scope:
The code uses threading, synchronization, interfaces, delegates and events. All of which are discussed below.
1. Main entry point in TestMessageLoopForm.vb.
Shared Sub Main()
Try
Thread.CurrentThread.Name = "Main App Thread"
Application.Run(New MessageLoopForm)
Catch exc As Exception
'This is from another library -- ExceptionInfoLib -- if you haven't downloaded it, comment out the call and "using" statement.
ExceptionInfoDisplay.DisplayInfo(exc)
End Try
End Sub 'Main
2. To receive messages from a thread the client must inherit and implement.
IMessageLoop (MessageInterface.cs) interface, which consists of three methods.
ThreadStartedProc( sender as Object, msg MessagePacket)
ThreadMessageProc( sender as Object, msg MessagePacket)
ThreadClosingProc( sender as Object, msg MessagePacket) 3. The Interfaces can be defined in one of two ways.
Public Sub ThreadStartedProc(ByVal sender As Object, ByVal msg As MessagePacket)
IMessageLoop.ThreadStartedProc(sender as Object, msg As MessagePacket) I choose the latter method. This makes the method available only through the interface and somewhat hides the implementation. In the case of multiple interface inheritance, where a method in each share the same name and parameters, this is the only means of implementation.
4. The MessageLoop Constructor (MessageLoop.cs).
Public Sub New(ByVal consumer As Object)
Try
Me.consumer = CType(consumer, IMessageLoop)
Catch exc As Exception
Dim loopExc As MessageLoopException = Nothing
loopExc = New MessageLoopException("IMessageLoop interface must be implemented", exc)
Throw loopExc
End Try
OnThreadStartedEvent = New ThreadStartedEventHandler Me.consumer.ThreadStartedProc)
OnThreadMessageEvent = New ThreadMessageEventHandler(Me.consumer.ThreadMessageProc)
OnThreadClosingEvent = New ThreadClosingEventHandler(Me.consumer.ThreadClosingProc)
End Sub 'New The client creates a MessageLoop instance with a reference to itself. This accomplishes two tasks. First, in casting the consumer to IMessageLoop, a NullReferenceException is thrown if the interface hasn't been implemented. Second, in making the delegates and events private, messageloop ensures that there not multiple callbacks attached to a delegate. Note that there are two alternative ways to determine if the IMessageLoop interface has been implemented. this.consumer = consumer as IMessageLoop - returns a "null" if not implemented. consumer is IMessageLoop - returns "false" if not implemented.
5. The Start method.
Public Sub Start()
If thread Is Nothing Then
CreateThread()
End If
If thread.IsAlive = False Then
waitOnStart = New AutoResetEvent(False)
thread.Start()
waitOnStart.WaitOne()
waitOnStart.Close()
waitOnStart = Nothing
Else
Dim exc As MessageLoopException = Nothing
exc = New MessageLoopException("Thread " + Me.Name + " is already running.")Throw exc
End If
End Sub 'Start A new thread is created on each start. Attempting to reuse a thread that has received a quit message results in a ThreadStateException. An AutoResetEvent is created so the method doesn't return until the message procedure has been entered. Finally the event is closed. If "Start" is called while the thread is running, an exception is thrown.
6. The Send & Post methods.
Public Sub SendMessage(ByVal msg As MessagePacket)
CheckIdValue(msg)
If msg.MsgId = MessageId.MsgQuit Then
PostMessage(msg)
Else
SyncLock msgList
msgList.Insert(0, msg)
End SyncLock
waitOnSend.WaitOne()
End If
End Sub 'SendMessage
Public Sub PostMessage(ByVal msg As MessagePacket)
CheckIdValue(msg)
SyncLock msgList
msgList.Add(msg)
End SyncLock
If msg.MsgId = MessageId.MsgQuit Then
thread.Join()
thread = Nothing
End If
End Sub 'PostMessage Three points to comment on in these methods. The ArrayList is locked to prevent multiple threads from accessing it simultaneously. In the "Send" method, a quit message is passed onto "Post" so all messages are dispatched before the thread is shutdown. To emulate the real world, "Send" waits until its message has been dispatched. The "thread.Join()", in the "Post" method, is only used in this example to prevent the application from exiting before a running thread. In the real world "Post" returns immediately.
7. The Dispatch loop.
Private Sub ThreadMessageLoop()
waitOnStart.Set()
DispatchStart()
While True
Dim msg As MessagePacket = Nothing
msg = GetMessage()
If Not (msg Is Nothing) Then
If msg.MsgId = MessageId.MsgQuit Then
Exit While
Else
DispatchMessage(msg)
waitOnSend.Set()
End If
End If
'normally set to 30 - to demonstrate send messages
Thread.Sleep(300)
End While
DispatchClosing()
End Sub 'ThreadMessageLoop Upon entering the dispatcher, the AutoResetEvent is signaled, permitting the "Start" method to return. After sending a start thread notification to the client, the dispatcher continues to loop until it receives a quit message. The dispatcher shipped to the client retrieves a waiting message. If the client thread is waiting, waitOnSend.Set() triggers its return.
8. Application shutdown (MessageLoopForm.cs).
Protected Overrides Sub OnClosing(ByVal evt As CancelEventArgs)
If btnStart.Enabled = False Then
btnStopPost.PerformClick()
End If
End Sub 'OnClosing If the application is closing and the Start button is disabled, then a thread is running and has to be closed. Ignoring this would cause the application's thread to exit, while the MessageLoop thread would merrily continue looping, causing the application to hang.