我的代码抛出一个错误,我认为应该是不可能的

时间:2011-12-23 11:49:14

标签: asp.net vb.net

  Public Enum ERight
    ECanInvite = 0
    ECanCreate = 1
    ECanDelete = 2
    etc...
  End Enum


  Public Enum EUserType
    EAdministrator = 0
    EPartner_level1 = 1
    EPartner_level2 = 2
    ENormalUser = 3
    ...etc
  End Enum

此{@ 1}}下方的子信息有时会引发此错误:rights.add

这怎么可能?

An item with the same key has already been added.

4 个答案:

答案 0 :(得分:0)

是否有什么阻止你的代码同时在不同的地方调用initRoles?

因为它是一个共享变量,所以一个调用可以进入,清除变量然后另一个调用可以进入,清除变量然后它们都开始向同一个引用添加值。取决于内部处理所有阻止的方式。

让我想起Dijkstra's Semaphores :)这让我退缩了。

无论如何,我可以建议将这个块包装成某个东西,以表明它是关键代码,并且每次应该一次运行吗?我在VB中有点生疏,但SyncLock语句看起来像是票证(链接中来自MSDN的代码和示例)。

答案 1 :(得分:0)

initRoles是一种实例方法,而rights是共享/静态字典。 在WebApplication中共享意味着它在所有http请求中共享,因为它们是不同的线程。可能会发生这样的情况:initRoles来自不同的请求,并且所有请求都在同时更改rights

你没有显示你在哪里调用initRoles,但我确信它是从HttpContext调用的。

如果您希望共享字典,还应该共享initRoles。 例如,您可以使用类CRights的共享构造函数来调用它:

Shared Sub New()
    initRoles()
End Sub

Private Shared Sub initRoles()
    rights = New Dictionary(Of EUserType, List(Of ERight))
    ' blah... '
End sub

这种方法要求字典在应用程序生命周期中仅初始化一次可能需要或不需要的内容。您也可以将方法设置为public,以便重新初始化它。来自您的用户/角色管理。

答案 2 :(得分:0)

发生错误是因为您在多线程环境中编写了Shared对象。

修复是添加一个锁(在这种情况下,对Clear()的调用变得多余):

Private Shared rights As Dictionary(Of ws_garuda.EUserType, List(Of ERight)) = Nothing
Private Shared rightsLock As Object = New Object()

Private Sub initRoles()
  SyncLock rightsLock
    rights = New Dictionary(Of EUserType, List(Of ERight))
    ' Set all rights to false for all roles
    For Each usertype As EUserType In DirectCast([Enum].GetValues(GetType(EUserType)), EUserType())
      rights.Add(usertype, New List(Of ERight))
    Next
  End SyncLock
End sub

答案 3 :(得分:0)

@RickNZ建议的修复是不够的。 Dictionary类不是线程安全的,任何使其使用线程安全的同步都需要读者和编写者。 .NET 4.0有ConcurrentDictionary类,there exist implementations for .NET 2.0

对于您的直接问题,最简单的解决方案是避免使用线程安全的Dictionary类,它是在将共享字典分配给共享的rights字段之前完全构造它。最简单的方法是将InitRoles更改为返回Dictionary

的共享函数
Private Shared rights As Dictionary(Of ws_garuda.EUserType, List(Of ERight)) = InitRoles()

Private Shared Function InitRoles() As Dictionary(Of ws_garuda.EUserType, List(Of ERight))
    Dim tempRights As Dictionary(Of ws_garuda.EUserType, List(Of ERight))
    tempRights = New Dictionary(Of EUserType, List(Of ERight))
    ' Set all rights to false for all roles
    For Each usertype As EUserType In DirectCast([Enum].GetValues(GetType(EUserType)), EUserType())
      tempRights.Add(usertype, New List(Of ERight))
    Next
    InitRoles = tempRights
End Function

但是,即使这样也不会使您的代码成为线程安全的。还需要同步对字典中List(Of ERight)值的任何访问。并且您的代码暗示此List不是只读的(它被初始化为空列表,这可能意味着它将被应用程序中其他地方的代码修改)。

我的建议是避免除了不可变对象之外的所有共享字段,除非你真的了解多线程。