COM+ Synchronization
The .NET framework provides many enterprise services that can be used to build enterprise applications, one of which is the COM+ method of synchronization. COM+ offers developers many helpful techniques such as transaction handling between objects, loosely coupled events, object pooling and synchronization, which we will discuss here, to name a few. This synchronization method allows the usage of a concept called a context to provide ways to lock code for synchronization. This method can be implemented on any class that is derived from ContextBoundObject
, or from any class that derives from ContextBoundObject
.
When deriving a class from ContextBoundObject
, the attribute <Syncronization()>
can be used. This tells the runtime to provide synchronization for the entire class by making each class instance only accessible by one thread at a time. This case study will give a brief overview of this topic, as it is out of the scope of the article. Entire books have been written on the subject of COM+. For further reading on COM+ get a copy of Professional Visual Basic Interoperability – COM and VB6 to .NET, ISBN 1-861005-65-2.
When you use the attribute, COM+ will create a proxy for you that will run all instances of your object in its context. COM+ will marshal all calls across this proxy where a performance penalty occurs. The service guarantees that only one thread is available to run each object at a time.
Earlier the timed methods of the WaitHandle
classes were discussed. Recall that the second parameter of the method was a boolean method that determined whether to release the synchronized context along with the object lock. If your classes use COM+ synchronization True
should be passed for this parameter or deadlocks are risked. True tells COM+ to exit its synchronized context before the runtime allows the thread to wait. This allows other threads to then get access to the context avoiding deadlocks. If you don’t exit the context, the . Net runtime will allow other threads access to the locked object since an exit method has been called. When the next thread acquires a lock on the locking object it will then try to enter the context, which is still locked resulting in a deadlock.
While COM+ synchronization provides another easy way to provide synchronization, be careful when using it. Many calls to a COM+ synchronized object will degrade your application greatly because of all the marshaling across the proxy. Be sure to test responsiveness when using it.
Apartments and Window’s Form Synchronization
Now that we have examined all the methods that Visual Basic offers for synchronization, we will take a look at Window’s Form projects and what apartment threading is. The most common types of threading on the Windows platform are single threaded apartments (STA) or multithreaded apartments (MTA). Window’s forms must be hosted in an STA apartment because some Window’s Form controls are based on standard Windows COM controls that require an STA environment. Background threads can still be utilized to update forms, but synchronization must be done differently. As we examine the two apartment styles, we will look at how to do correct synchronization with Window’s Forms.
By default all Windows’ Form projects in Visual Basic are STA. Visual Basic applies the <STATHREAD()>
attribute to the main entry point in the application for you behind the scenes. While you could override this attribute and change it to an MTA apartment, you should not or problems will occur with the COM controls as discussed above.
So what is an STA apartment?The apartment concept comes from the early COM days. Basically, STA means that only one thread can access an object, the thread that created it. Any future access to the object must also be done on the original thread. This is the one key reason why you should never update a control on a Window’s Form from another thread. Most COM objects require STA.
MTA, sometimes called free threading, is much harder to program than STA. This is another reason why we encounter STA COM components most of the time. MTA means that more than one thread can access an object at any given point in time safely. When programming for MTA, you must be sure to include good synchronization and design as discussed in the case study. Any number of threads could be accessing objects in your library at any time.
The type of threading model that the current thread is using can be determined simply with the following code.
Dim sThreadType As String
sThreadType = Thread.CurrentThread.ApartmentState.ToString()
MessageBox.Show(sThreadType)
sThreadType will equal “STA” or “MTA” after the call. There is also an ApartmentState
object that can be set to Thread.CurrentThread.AppartmentState()
.
Dim Apt as ApartmentState
Apt = Thread.CurrentThread.ApartmentState()
MessageBox.Show(apt.ToString())
Window’s Form classes provide built in methods to update GUI elements from other threads. These methods should be used exclusively. The methods are called Invoke
, BeginInvoke
, EndInvoke
and CreateGraphics
. All of the methods can be called from any thread. When called, the methods provide a way to work with the control from the main Window’s Form thread. Let’s see how we can use the methods.
The Invoke
method takes a delegate for a parameter. A delegate is basically a variable that points to a method. The variable in this case tells the Invoke
method what function to run. This delegate is run under the control’s owner thread and not the calling thread, preserving the STA style. Let’s take a look at a simple example that adds entries to a textbox control using a separate thread. A button and a multi-line textbox are added to a Window’s Form.
Private Sub btnStart_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnStart.Click
Dim Thread1 As Thread
Thread1 = New Thread(AddressOf Thread1Work)
Thread1.Start()
End Sub
Private Delegate Sub DelAddItem()
Private Sub Thread1Work()
Dim del As DelAddItem
del = New DelAddItem(AddressOf DelegateWork)
txtList.Invoke(del)
Console.WriteLine("Thread 1 Done")
End Sub
Private Sub DelegateWork()
Dim i As Integer
For i = 0 To 100
txtList.Text = txtList.Text + "A New Line: " & i.ToString(+vbCrLf)
Next 'i
Console.WriteLine("Delegate Done")
End Sub
To call Invoke
, a delegate sub is created. This sub simply adds a new line to the textbox with the words “A New Line”. When our new thread is started a new instance of the delegate is created. The new delegate is then passed to txtList. Invoke
updating the text.
The Invoke method runs any code in the delegate synchronously on the thread. The output from the run will show this:
Delegate Done Thread 1 Done
Thread 1 will not continue running until the delegate is finished.
Sometimes asynchronous calls are preferred. The BeginInvoke
and EndInvoke
allow updating the GUI using built in asynchronous technology in the framework. The two methods take the same delegate that Invoke
did. They only call the code asynchronously. EndInvoke
will return the resulting value from an asynchronous BeginInvoke
call. If the BeginInvoke
is still running, EndInvoke
will block until the BeginInvoke
call finishes. It will not terminate the BeginInvoke
call. An example is below.
Private Sub btnStart_Click(ByVal sender As System.Object,
ByVal e As System.EventArgs) Handles btnStart.Click
Dim T hread1 As Thread
Thread1 = New Thread(AddressOf Thread1Work)
Thread(1.Start())
End Sub
Private Delegate Sub DelAddItem()
Private Sub Thread1Work()
Dim del As DelAddItem
Dim Result As IAsyncResult
del = New DelAddItem(AddressOf DelegateWork)
Result = txtList.BeginInvoke(del)
Console.WriteLine("Thread 1 Done")
Console.WriteLine(Result.IsCompleted.ToString())
txtList.EndInvoke(Result)
Console.WriteLine(Result.IsCompleted.ToString())
End Sub
Private Sub DelegateWork()
Dim i As Integer
For i = 0 To 100
txtList.Text = txtList.Text + "A New Line: " & i.ToString(+vbCrLf)
Next 'i
Console.WriteLine("Delegate Done")
End Sub
Output:
Thread 1 Done False Delegate Done True
As we see from the output, Thread 1 completed before the delegate finished. Then the first call to Result.IsCompleted
returns false, signifying that the delegate is still running. Thread 1 is then put to sleep with the EndInvoke
call allowing the delegate time to finish. The next call to Result.IsCompleted
returns true.
The code also shows two methods of getting the status of an asynchronous call. The first method was the line Result = txtList.BeginInvoke(del).
The Result
variable will contain the current results of the asynchronous call. The other method is with the EndInvoke
call, which we said earlier, would block until the asynchronous call is finished. The last output of true shows that this behavior happened.
When using graphics drawing methods with Window’s Forms you must be sure to do all work on the main thread also. The CreateGraphics
method makes sure of this for you. It can be called from other threads safely like the invoke methods. The Graphics
object returned will run all calls in the correct thread for you. The Graphics
object is considered thread safe so no additional locking objects are necessary.
Comments