我有以下代码:
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
答案 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;你自己,并以线程安全的方式这样做。