我有一个包含静态集合的类,用于将登录用户存储在ASP.NET MVC应用程序中。我只是想知道下面的代码是否是线程安全的。每当我向onlineUsers集合添加或删除项目时,是否需要锁定代码。
public class OnlineUsers
{
private static List<string> onlineUsers = new List<string>();
public static EventHandler<string> OnUserAdded;
public static EventHandler<string> OnUserRemoved;
private OnlineUsers()
{
}
static OnlineUsers()
{
}
public static int NoOfOnlineUsers
{
get
{
return onlineUsers.Count;
}
}
public static List<string> GetUsers()
{
return onlineUsers;
}
public static void AddUser(string userName)
{
if (!onlineUsers.Contains(userName))
{
onlineUsers.Add(userName);
if (OnUserAdded != null)
OnUserAdded(null, userName);
}
}
public static void RemoveUser(string userName)
{
if (onlineUsers.Contains(userName))
{
onlineUsers.Remove(userName);
if (OnUserRemoved != null)
OnUserRemoved(null, userName);
}
}
}
答案 0 :(得分:13)
这绝对不是线程安全的。任何时候2个线程正在做某事(在Web应用程序中很常见),混乱是可能的 - 异常或静默数据丢失。
是的,您需要某种同步,例如lock
;而static
对于数据存储IMO来说通常是一个非常糟糕的主意(除非非常小心地对待并限于配置数据之类的东西)。
此外 - static
事件因使对象图意外存活的好方法而臭名昭着。也要谨慎对待;如果您只订阅一次,那么很好 - 但不要按订阅订阅等。
此外 - 它不仅仅是锁定操作,因为这一行:
return onlineUsers;
返回您的列表,现在不受保护。必须同步 所有 对项目的访问权限。就个人而言,我会返回副本,即
lock(syncObj) {
return onlineUsers.ToArray();
}
最后,从此处返回.Count
可能会造成混淆 - 因为无论如何都不能保证Count
。它仅在那个时间点是信息。
答案 1 :(得分:4)
是的,您需要锁定onlineUsers
以使代码线程安全。
一些注意事项:
使用HashSet<string>
代替List<string>
可能是一个好主意,因为对于这样的操作(Contains
和Remove
来说效率要高得多)。但这并没有改变锁定要求的任何内容。
如果某个类只有静态成员,则可以将其声明为“静态”。
答案 2 :(得分:1)
是的,您确实需要锁定代码。
object padlock = new object
public bool Contains(T item)
{
lock (padlock)
{
return items.Contains(item);
}
}
答案 3 :(得分:1)
是。您需要在读取或写入集合之前锁定集合,因为可能会从不同的线程池工作程序添加多个用户。你也应该在计算时也这样做,不过如果你不关心可能不是问题的100%准确性。
答案 4 :(得分:1)
根据Lucero的回答,您需要锁定onlineUsers。还要注意你的班级客户对GetUsers()返回的onlineUsers做了什么。我建议你改变你的界面 - 例如使用IEnumerable<string> GetUsers()
并确保在其实现中使用锁。像这样:
public static IEnumerable<string> GetUsers() {
lock (...) {
foreach (var element in onlineUsers)
yield return element;
// We need foreach, just "return onlineUsers" would release the lock too early!
}
}
请注意,如果用户尝试调用其他使用lock的OnlineUsers方法,此实现可能会使您遇到死锁,同时仍在迭代GetUsers()的结果。
答案 5 :(得分:1)
该代码本身不是线程安全的。
我不会就你的“设计”提出任何建议,因为你没有问过任何建议。我假设你找到了这些静态成员的正当理由,并像你一样公开你的列表内容。
但是,如果你想让你的代码线程安全,你基本上应该使用一个锁对象来锁定,并用lock语句包装你方法的内容:
private readonly object syncObject = new object();
void SomeMethod()
{
lock (this.syncObject)
{
// Work with your list here
}
}
请注意,正在筹集的这些事件有可能在很长一段时间内保持锁定,具体取决于代表的行为。 在将列表声明为volatile时,可以省略NoOfOnlineUsers属性的锁定。但是,如果您希望Count值在某个时刻保持不变,请在那里使用锁定。
正如其他人在此建议的那样,即使使用锁定,直接暴露您的列表仍然会对其内容造成“威胁”。我会像Mark Gravell建议的那样返回副本(这应该适合大多数目的)。
现在,既然你说你在ASP.NET环境中使用它,那么值得一提的是,所有本地变量和成员变量以及它们的成员变量(如果有的话)都是线程安全的。