如果中间存在外部api调用,如何处理lock语句

时间:2012-07-24 17:30:23

标签: c# asp.net concurrency

我有以下代码:

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

2 个答案:

答案 0 :(得分:4)

你可以将GenerateSession移出锁定,如果你能负担得起两次锁定,如果偶尔会产生sessionId但从未使用过它就可以了。

这样的事情:

 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。如果两个线程同时使用相同的school和major执行此函数,则两个线程都将生成会话ID,但只有一个线程将成功创建新的soloUser并将其添加到集合中。丢失的线程将在集合中找到soloUser并将其从集合中删除 - 而不是使用刚刚生成的sessionId。此时,两个线程将引用具有相同sessionId的相同soloUser,这似乎是目标。

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

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

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

答案 1 :(得分:0)

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

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

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

请注意,这是一个包,而不是一个包。因此,您必须围绕&#34;没有重复的代码进行编码&#34;你自己,并以线程安全的方式这样做。