2012-07-24 31 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

注意,這是一個包,沒有一套。因此,您必須自己編寫「不重複」代碼,並以線程安全的方式執行此操作。