2

我有以下代码:

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)andTokenHelper.GenerateModeratorToken(session)因为我意识到它们可能发生在锁之外,但每个SoloUser都有一个名为的属性SessionId,这是为每个用户生成的。该GenerateSession方法也将是一种需要一点时间的方法。SessionId每个用户在被添加到集合之前都需要拥有其中一个

4

2 回答 2

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。

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

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

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

Make sure the cost of GenerateSession is high enough to justify this extra run-around. If GenerateSession takes nanoseconds to complete, you don't need all this - just leave it in the lock as originally written. If GenerateSession takes "a long time" (a second or more? 500ms or more? can't say), then moving it out of the lock is a good idea to prevent other uses of the shared list from having to wait.

于 2012-07-24T18:05:01.703 回答
0

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

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

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

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

于 2012-07-24T17:35:27.477 回答