如何使列表线程的迭代安全

时间:2016-01-21 11:55:03

标签: .net vb.net multithreading thread-safety locking

我有一个包含Dictionary的类,当调用LoadFromDb方法时,该类从数据库加载,可以从任意数量的线程调用多次。

然后我有一个Employees属性,可以被任何线程调用。

作为一个例子:

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

我想确保LoadFromDb只能在任何时候调用一次。如果有锁定,其他呼叫应该中止。

但是我想在此时锁定Employees属性,但是当没有调用LoadFromDb时,我想允许多个线程读取值。

这基本上是解决方案,但我想知道如何使用SyncLockMutexSemaphore或其他可确保线程安全的方法来实现此目标。我最初尝试了SyncLock,但是当没有调用Employees时,这会阻止多个线程访问LoadFromDb属性:

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

1 个答案:

答案 0 :(得分:2)

使用Threading.Monitor方法可能是最好的方法。没有明显的原因进行测试。您知道由于rnd.Next的调用方式,有可能生成零记录。

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

编辑:

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