SyncLock Statement
Acquires an exclusive lock for a statement block before executing the block.
Syntax
SyncLock lockobject
[ block ]
End SyncLock
Parts
lockobject
Required. Expression that evaluates to an object reference.
block
Optional. Block of statements that are to execute when the lock is acquired.
End SyncLock
Terminates a SyncLock
block.
Remarks
The SyncLock
statement ensures that multiple threads do not execute the statement block at the same time. SyncLock
prevents each thread from entering the block until no other thread is executing it.
The most common use of SyncLock
is to protect data from being updated by more than one thread simultaneously. If the statements that manipulate the data must go to completion without interruption, put them inside a SyncLock
block.
A statement block protected by an exclusive lock is sometimes called a critical section.
Rules
Branching. You cannot branch into a
SyncLock
block from outside the block.Lock Object Value. The value of
lockobject
cannot beNothing
. You must create the lock object before you use it in aSyncLock
statement.You cannot change the value of
lockobject
while executing aSyncLock
block. The mechanism requires that the lock object remain unchanged.You can't use the Await operator in a
SyncLock
block.
Behavior
Mechanism. When a thread reaches the
SyncLock
statement, it evaluates thelockobject
expression and suspends execution until it acquires an exclusive lock on the object returned by the expression. When another thread reaches theSyncLock
statement, it does not acquire a lock until the first thread executes theEnd SyncLock
statement.Protected Data. If
lockobject
is aShared
variable, the exclusive lock prevents a thread in any instance of the class from executing theSyncLock
block while any other thread is executing it. This protects data that is shared among all the instances.If
lockobject
is an instance variable (notShared
), the lock prevents a thread running in the current instance from executing theSyncLock
block at the same time as another thread in the same instance. This protects data maintained by the individual instance.Acquisition and Release. A
SyncLock
block behaves like aTry...Finally
construction in which theTry
block acquires an exclusive lock onlockobject
and theFinally
block releases it. Because of this, theSyncLock
block guarantees release of the lock, no matter how you exit the block. This is true even in the case of an unhandled exception.Framework Calls. The
SyncLock
block acquires and releases the exclusive lock by calling theEnter
andExit
methods of theMonitor
class in the System.Threading namespace.
Programming Practices
The lockobject
expression should always evaluate to an object that belongs exclusively to your class. You should declare a Private
object variable to protect data belonging to the current instance, or a Private Shared
object variable to protect data common to all instances.
You should not use the Me
keyword to provide a lock object for instance data. If code external to your class has a reference to an instance of your class, it could use that reference as a lock object for a SyncLock
block completely different from yours, protecting different data. In this way, your class and the other class could block each other from executing their unrelated SyncLock
blocks. Similarly locking on a string can be problematic since any other code in the process using the same string will share the same lock.
You should also not use the Me.GetType
method to provide a lock object for shared data. This is because GetType
always returns the same Type
object for a given class name. External code could call GetType
on your class and obtain the same lock object you are using. This would result in the two classes blocking each other from their SyncLock
blocks.
Examples
Description
The following example shows a class that maintains a simple list of messages. It holds the messages in an array and the last used element of that array in a variable. The addAnotherMessage
procedure increments the last element and stores the new message. Those two operations are protected by the SyncLock
and End SyncLock
statements, because once the last element has been incremented, the new message must be stored before any other thread can increment the last element again.
If the simpleMessageList
class shared one list of messages among all its instances, the variables messagesList
and messagesLast
would be declared as Shared
. In this case, the variable messagesLock
should also be Shared
, so that there would be a single lock object used by every instance.
Code
Class simpleMessageList
Public messagesList() As String = New String(50) {}
Public messagesLast As Integer = -1
Private messagesLock As New Object
Public Sub addAnotherMessage(ByVal newMessage As String)
SyncLock messagesLock
messagesLast += 1
If messagesLast < messagesList.Length Then
messagesList(messagesLast) = newMessage
End If
End SyncLock
End Sub
End Class
Description
The following example uses threads and SyncLock
. As long as the SyncLock
statement is present, the statement block is a critical section and balance
never becomes a negative number. You can comment out the SyncLock
and End SyncLock
statements to see the effect of leaving out the SyncLock
keyword.
Code
Imports System.Threading
Module Module1
Class Account
Dim thisLock As New Object
Dim balance As Integer
Dim r As New Random()
Public Sub New(ByVal initial As Integer)
balance = initial
End Sub
Public Function Withdraw(ByVal amount As Integer) As Integer
' This condition will never be true unless the SyncLock statement
' is commented out:
If balance < 0 Then
Throw New Exception("Negative Balance")
End If
' Comment out the SyncLock and End SyncLock lines to see
' the effect of leaving out the SyncLock keyword.
SyncLock thisLock
If balance >= amount Then
Console.WriteLine("Balance before Withdrawal : " & balance)
Console.WriteLine("Amount to Withdraw : -" & amount)
balance = balance - amount
Console.WriteLine("Balance after Withdrawal : " & balance)
Return amount
Else
' Transaction rejected.
Return 0
End If
End SyncLock
End Function
Public Sub DoTransactions()
For i As Integer = 0 To 99
Withdraw(r.Next(1, 100))
Next
End Sub
End Class
Sub Main()
Dim threads(10) As Thread
Dim acc As New Account(1000)
For i As Integer = 0 To 9
Dim t As New Thread(New ThreadStart(AddressOf acc.DoTransactions))
threads(i) = t
Next
For i As Integer = 0 To 9
threads(i).Start()
Next
End Sub
End Module