2012-07-24 33 views
2

之间,我有以下代码:如何处理锁声明,如果有外部API调用

private static HashSet<SoloUser> soloUsers = new HashSet<SoloUser>(); 

    public void findNewPartner(string School, string Major) 
    { 
     lock (soloUsers) 
     { 
      SoloUser soloUser = soloUsers.FirstOrDefault(s => (s.School == School) && (s.Major == Major)); 
      MatchConnection matchConn; 
      if (soloUser != null) 
      { 
       if (soloUser.ConnectionId != Context.ConnectionId) 
       {                  
        soloUsers.Remove(soloUser); 
       } 

      } 
      else 
      { string sessionId = TokenHelper.GenerateSession();          

       soloUser = new SoloUser 
       { 
        Major = Major, 
        School = School, 
        SessionId = sessionId, 
        ConnectionId = Context.ConnectionId 
       }; 

       soloUsers.Add(soloUser); 


      } 

     } 

    } 

TokenHelper.GenerateToken(soloUser.Session)TokenHelper.GenerateModeratorToken(session);可能是危险的,因为他们可能需要一些时间来生成令牌。这会锁定所有用户,这可能是一个问题?有没有对这个逻辑的任何解决方法,以便我仍然可以保持一切线程安全?

编辑: 我删除了TokenHelper.GenerateToken(soloUser.Session)TokenHelper.GenerateModeratorToken(session),因为我意识到,他们可以锁定外发生,但每个SoloUser有一个名为SessionId财产,这是为每个用户生成。 GenerateSession方法也是一个需要一点时间的方法。每个用户在添加到集合之前需要具有以下其中一个SessionId s

+0

您是否在关于锁定UI线程或关于锁定其他可能也想使用此数据的函数? – 2012-07-24 17:35:17

+0

@JeffBridgman他使用ASP.NET。没有“UI线程”。 – 2012-07-24 17:37:37

+0

哦,我傻...没有注意。抱歉! – 2012-07-24 17:38:39

回答

4

如果您可以负担两次锁,并且如果偶尔会生成sessionId但从未使用过,那么您可以将GenerateSession从锁中移出。

事情是这样的:

public void findNewPartner(string School, string Major) 
    { 
     SoloUser soloUser = null; 

     lock (soloUsers) 
     { 
      soloUser = soloUsers.FirstOrDefault(s => (s.School == School) && (s.Major == Major)); 
     } 

     string sessionId = null; 

     // will we be creating a new soloUser? 
     if (soloUser == null) 
     { 
      // then we'll need a new session for that new user 
      sessionId = TokenHelper.GenerateSession(); 
     } 

     lock (soloUsers) 
     { 
      soloUser = soloUsers.FirstOrDefault(s => (s.School == School) && (s.Major == Major)); 
      if (soloUser != null) 
      { 
       // woops! Guess we don't need that sessionId after all. Oh well! Carry on... 
       if (soloUser.ConnectionId != Context.ConnectionId) 
       {                  
        soloUsers.Remove(soloUser); 
       } 

      } 
      else 
      { 
       // use the sessionid computed earlier 
       soloUser = new SoloUser 
       { 
        Major = Major, 
        School = School, 
        SessionId = sessionId, 
        ConnectionId = Context.ConnectionId 
       }; 

       soloUsers.Add(soloUser); 

     } 

    } 

这基本上没有看到一个快速锁定,如果一个新的soloUser需要构建,如果是这样,那么我们需要生成一个新的会话。生成新的会话发生在锁之外。然后我们重新获得锁定并执行原始操作。在构建新的soloUser时,它使用在锁之外构建的sessionId。

此模式可能会生成从不使用的sessionIds。如果两个线程在同一学校和专业同时执行此功能,两个线程都将生成会话ID,但只有其中一个线程会成功创建一个新的SOLOUser并将其添加到集合中。失败的线程将在集合中找到soloUser并将其从集合中移除 - 而不是使用它刚刚生成的sessionId。在这一点上,两个线程都会引用同一个单独的用户,它们具有相同的sessionId,这似乎是目标。

如果sessionIds具有与它们相关联的资源(例如数据库中的条目),但是sessionId老化时会清除这些资源,那么像这样的冲突会产生一些额外的噪音,但总体上不会影响系统。

如果生成的sessionId与它们没有任何关联需要清理或老化,那么您可以考虑丢失我的示例中的第一个锁,并且只是始终生成一个sessionId,无论是否需要。这可能不是一个可能的情况,但是我之前在特殊情况下使用了这种“混杂”技巧,以避免跳入和跳出高流量锁。如果制造起来很便宜而且价格昂贵,那么就放弃并小心锁定。

确保GenerateSession的成本高到足以证明这种额外的运行。如果GenerateSession需要几纳秒才能完成,则不需要所有这些 - 只需将其保留在最初写入的锁中即可。如果GenerateSession需要“很长时间”(一秒或更多?500ms或更多?不能说),那么将它从锁中移出是防止共享列表的其他用途不得不等待的好主意。

+0

真的,对不起,我刚刚意识到这一点,我改变了我上面的代码中,被添加到集合中的SoloUser对象需要运行'GenerateSession'方法,以便更新'SoloUser'完成 – anthonypliu 2012-07-24 18:06:05

+0

。签出新的代码。 – dthorpe 2012-07-24 18:25:26

0

最好的解决方案可能是使用ConcurrentBag<T>

http://msdn.microsoft.com/en-us/library/dd381779

我假设你正在使用.NET 4

注意,这是一个包,没有一套。因此,您必须自己编写“不重复”代码,并以线程安全的方式执行此操作。