Reputation: 27322
I have a class that contains a Dictionary
that is loaded from the DB when the LoadFromDb
method is called, this can be called many times from any number of threads.
I then have an Employees
property which may be called by any thread.
As an exmaple:
Public Class Employees
Private _employeesDictionary As New SortedDictionary(Of Int32, Employee)
Public ReadOnly Property Employees As IList(Of Employee)
Get
Return _employeesDictionary.Values.ToList
End Get
End Property
Public Sub loadFromDb()
Static rnd As New Random
_employeesDictionary = New SortedDictionary(Of Int32, Employee)
'generate a random number of employees
Dim numEmpsToAdd = rnd.Next(101)
For i = 0 To numEmpsToAdd
_employeesDictionary.Add(i + 1, New Employee With {.ClockNo = i + 1, .Name = $"Name:{i + 1}"})
Next
End Sub
End Class
I want to ensure that LoadFromDb
can only be called once at any one time. Additional calls should abort if there is a lock.
However I want to also lock the Employees property from being read at this point, but when LoadFromDb
is not being called I want to allow multiple threads to read the values.
This is basically the solution, but I want to know how I can achieve this using SyncLock
, Mutex
, Semaphore
, or something else which will ensure thread safety. I initially tried a SyncLock
, but this blocks multiple threads from accessing the Employees
property when LoadFromDb
is not being called:
Public Class Employees
Private _employeesDictionary As New SortedDictionary(Of Int32, Employee)
Private locked As Boolean
Public ReadOnly Property Employees As IList(Of Employee)
Get
Do While locked
'wait for lock to be released
Loop
Return _employeesDictionary.Values.ToList
End Get
End Property
Public Sub loadFromDb()
Static rnd As New Random
If locked Then Exit Sub
locked = True
Try
_employeesDictionary = New SortedDictionary(Of Int32, Employee)
'generate a random number of employees
Dim numEmpsToAdd = rnd.Next(101)
For i = 0 To numEmpsToAdd
_employeesDictionary.Add(i + 1, New Employee With {.ClockNo = i + 1, .Name = $"Name:{i + 1}"})
Next
Catch ex As Exception
Throw ex
Finally
locked = False
End Try
End Sub
End Class
Upvotes: 0
Views: 60
Reputation: 11773
Using Threading.Monitor methods might be the best approach. Not tested for obvious reasons. You do know that there is a possibility of generating zero records because of how the rnd.Next is called.
Public Class Employees
Private _employeesDictionary As New SortedDictionary(Of Int32, Employee)
Private locked As New Object
Public ReadOnly Property Employees As IList(Of Employee)
Get
Dim rv As New List(Of Employee)
Threading.Monitor.Enter(locked)
'wait for lock to be released
rv = _employeesDictionary.Values.ToList
Threading.Monitor.Exit(locked)
Return rv
End Get
End Property
Private Shared rnd As New Random 'only one needed
Public Sub loadFromDb()
Try
If Threading.Monitor.TryEnter(locked) Then
_employeesDictionary = New SortedDictionary(Of Int32, Employee)
'generate a random number of employees
Dim numEmpsToAdd As Integer = rnd.Next(101)
For i As Integer = 0 To numEmpsToAdd
_employeesDictionary.Add(i + 1, New Employee With {.ClockNo = i + 1, .Name = $"Name:{i + 1}"})
Next
Threading.Monitor.Exit(locked)
End If
Catch ex As Exception
Throw ex
Finally
Threading.Monitor.Exit(locked)
End Try
End Sub
End Class
edit:
Public Class Test
Private _employeesDictionary As New SortedDictionary(Of Int32, Int32)
Public ReadOnly Property Employees As IList(Of Int32)
Get
Dim rv As New List(Of Int32)
'wait for a Semaphore to be released
mySemaPhore.WaitOne()
rv = _employeesDictionary.Values.ToList
mySemaPhore.Release()
Return rv
End Get
End Property
Const maxThreads As Integer = 4
Private mySemaPhore As New Threading.Semaphore(maxThreads, maxThreads)
Private locked As New Object
Private Shared rnd As New Random 'only one needed
Public Sub loadFromDb()
Try
Dim tempDict As New SortedDictionary(Of Int32, Int32)
'generate a random number of employees
Dim numEmpsToAdd As Integer = rnd.Next(6)
For i As Integer = 0 To numEmpsToAdd
tempDict.Add(i + 1, i + 1)
Next
If Threading.Monitor.TryEnter(locked) Then
'get all semaphores
For x As Integer = 1 To maxThreads
mySemaPhore.WaitOne()
Next
_employeesDictionary = New SortedDictionary(Of Int32, Int32)(tempDict)
'release all semaphores
For x As Integer = 1 To maxThreads
mySemaPhore.Release()
Next
Threading.Monitor.Exit(locked)
End If
tempDict.Clear()
Catch ex As Exception
Throw ex
Finally
If Threading.Monitor.IsEntered(locked) Then
Threading.Monitor.Exit(locked)
End If
End Try
End Sub
End Class
Upvotes: 2