如果两者之间有外部api调用,如何处理lock语句



我有以下代码:

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);

            }
        } 
    }

CCD_ 1和CCD_ 2可能是危险的,因为它们可能需要一段时间来生成令牌。这会将所有用户锁定一段时间,这可能是个问题吗?这个逻辑有什么变通办法吗,这样我仍然可以保证一切都是线程安全的?

编辑:我删除了TokenHelper.GenerateToken(soloUser.Session)TokenHelper.GenerateModeratorToken(session),因为我意识到它们可以在锁之外发生,但每个SoloUser都有一个名为SessionId的属性,这是为每个用户生成的。CCD_ 7方法也将是花费一段时间的方法。每个用户在添加到集合之前都需要拥有其中一个SessionId

如果您能够承受两次锁,并且如果偶尔生成一个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毫秒或更长时间,不能说),那么将其移出锁是一个好主意,可以防止共享列表的其他用途不得不等待。

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

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

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

请注意,这是一个袋子,而不是一套。因此,您必须自己围绕"无重复"进行编码,并以线程安全的方式进行编码。

最新更新