【问题标题】:How to handle lock statement if there is an external api call in between如果两者之间有外部 api 调用,如何处理 lock 语句
【发布时间】:2012-07-24 17:30:23
【问题描述】:

我有以下代码:

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 方法也需要一些时间。每个用户都需要拥有其中一个SessionIds 才能被添加到集合中

【问题讨论】:

  • 您是否担心锁定 UI 线程或锁定其他可能也想使用这些数据的功能?
  • @JeffBridgman 他正在使用 ASP.NET。没有“UI 线程”。
  • 哦,我真傻……没注意。对不起!
  • 如果生成的令牌是唯一的,那么您可以只将您实际访问哈希集的代码放在锁内。
  • 您实际在哪里使用生成的令牌?

标签: c# asp.net concurrency


【解决方案1】:

如果您有能力获得两次锁定并且如果偶尔生成但从未使用过的 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。

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

如果 sessionId 有与之关联的资源(例如数据库中的条目),但这些资源会在 sessionId 过期时被清理,那么这样的冲突会产生一点额外的噪音,但总体上不会影响系统。

如果生成的 sessionId 没有与它们相关的任何需要清理或老化的东西,那么您可能会考虑在我的示例中丢失第一个锁并始终生成一个 sessionId,无论它是否需要。这可能不太可能发生,但我之前在特殊情况下使用过这种“混杂”技巧以避免跳入和跳出高流量锁。如果创建成本低而锁定成本高,那么就放弃创建并小心锁定。

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

【讨论】:

  • 真的,很抱歉我才意识到这一点,我更改了上面的代码,添加到集合中的 SoloUser 对象需要运行 GenerateSession 方法才能使 SoloUser 完整
【解决方案2】:

最好的解决方案可能是使用ConcurrentBag&lt;T&gt;

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

我假设您使用的是 .NET 4。

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

【讨论】:

    猜你喜欢
    • 2011-08-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-16
    • 1970-01-01
    • 1970-01-01
    • 2019-12-23
    • 1970-01-01
    相关资源
    最近更新 更多