Threading issues may affect Visual Basic 6 ActiveX DLLs or UserControls that you upgrade to Visual Basic .NET, especially since the Visual Basic Upgrade Wizard does not make your code thread-safe when upgrading it. You will need to make manual changes to your codeas explained later in this sectionto make it thread-safe.
Such threading issues are uncommon, since components created in Visual Basic 6 are thread-safe by nature. Thread safety is accomplished in Visual Basic 6 by letting only a single thread access a component. Even if multiple threads are attempting to gain access to the component, all requests are synchronized through the single accessor thread associated with the component.
Visual Basic .NET components (including UserControls), on the other hand, are not thread-safe. Multiple threads are allowed to access the component simultaneously. There is no synchronization of threads on behalf of a Visual Basic .NET component. As a result, code that is thread-safe in Visual Basic 6 becomes unsafe after being upgraded to Visual Basic .NET. This change will affect you if you are running your component in a multithreaded environment such as Internet Explorer, Internet Information Services, or COM+. If you are not sure whether your component will be run in a multithreaded environment, it is always best to err on the side of caution and ensure that your component is thread-safe.
When more than one thread is executing your code, each thread has access to the same shared data: member variables and global variables. Allowing more than one thread to manipulate shared data at the same time can lead to problems. For example, if two threads are attempting to increment a count of items in a collection simultaneously, the count may be end up being incremented by 1 and not 2. This will happen if both threads obtain the current count at about the same time and add 1. Neither thread will see what the other thread is doing. We call this a synchronization problem, since it involves a situation in which only one thread at a time should be allowed to perform the operation.
For example, a synchronization problem can occur when one thread caches a global or member variable value in a local variable and performs a calculation on that variable. Lets take a look at the following Visual Basic .NET code.
Public Class GreedyBank
Private m_Balance As Decimal
Public Sub New()
m_Balance = 20
End Sub
Public Function Withdraw(ByVal Amount As Decimal) As String
'Protect against negative balance
If m_Balance >= Amount Then
m_Balance = m_Balance - Amount
Else
Return "The amount you requested will overdraw " & _
"your account by $" & Amount m_Balance
End If
End Function
End Class
This example is pretty straightforward. A client application calls Withdraw. Withdraw takes the current customer balance and deducts the requested withdrawal amount. To ensure that the customers account does not become overdrawn by the transaction, the function checks to make sure that the customer has enough money to cover the withdrawal amount.
This works great when only one thread at a time is executing the function. However, you can run into problems if two threads are executing the function simultaneously.
Lets suppose that the first thread has executed up to the following line:
m_Balance = m_Balance - Amount
The customers balance checked out, so the thread is ready to deduct the Amount. Assume that the customer has just enough money ($20) to cover the withdrawal request of $20. Just before the first thread executes the above statement, a second thread comes along, requesting an amount of $20 and completing the following statement:
If m_Balance >= Amount Then
The second thread also sees that the bank balance, $20, is sufficient to cover the withdrawal request and proceeds to the next line:
m_Balance = m_Balance - Amount
At this point, the customer is rewarded with an account overdraft and the fees that accompany it. The first thread subtracts the withdrawal, leaving a balance of $0. The second thread, having successfully bypassed the sufficient-balance-to-cover-withdraw-amount check, then executes the same line, leaving a net balance of $20. Here code that on the surface appears to be perfectly legitimate leads to unanticipated resultsalthough the customer would need to submit two withdrawal requests simultaneously to encounter the problem.
Using a SyncLock Block
To fix this synchronization problem, you need to make sure that only one thread at a time has access to your shared member or global variables. In other words, you need to synchronize access to your data, letting only one thread at time execute certain blocks of code that manipulate shared data. You can do this by using the new Visual Basic .NET keyword SyncLock.
A SyncLock block ensures that only one thread at a time is executing code within the block. In the earlier code example, we can fix our negative balance problem by placing a SyncLock around the contents of the Withdraw function:
SyncLock GetType(GreedyBank)
'Protect against negative balance
If m_Balance >= Amount Then
m_Balance = m_Balance - Amount
Else
Return "The amount you requested will overdraw " & _
"your account by $" & Amount m_Balance
End If
End SyncLock
If there is any chance that your Visual Basic .NET component or UserControl will run in a multithreaded environment, we highly recommend using SyncLock blocks to protect your shared data. Taking the extra time to do this work up front can save you from grief down the road.