对.net中静态成员的并发访问

本文关键字:并发 访问 静态成员 net | 更新日期: 2023-09-27 18:02:24

我有一个类,它包含一个静态集合,用于在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);
        }
    }
}

对.net中静态成员的并发访问

这绝对不是线程安全的。任何时候两个线程在做某件事(在web应用程序中很常见),混乱都是可能的——异常,或者无声的数据丢失。

是的,你需要某种同步,比如lock;在我看来,static通常是一个非常糟糕的数据存储方法(除非处理得非常仔细,并且仅限于配置数据之类的东西)。

也- static事件是臭名昭著的一个很好的方式来保持对象图意外存活。也要谨慎对待这些人;如果你只订阅一次,没关系,但不要每次都订阅。

也不仅仅是锁定操作,因为这一行:

return onlineUsers;

返回列表,现在不受保护。所有对项的访问必须同步。我个人会返回一个副本,即

lock(syncObj) {
    return onlineUsers.ToArray();
}

最后,从这样返回.Count可能会令人困惑-因为它不能保证在任何时候仍然是Count。在那个时间点是信息

是的,您需要锁定onlineUsers以使该代码线程安全。

注意事项:

  • 使用HashSet<string>代替List<string>可能是一个好主意,因为它对于这样的操作(特别是ContainsRemove)更有效。但是,这不会改变锁的要求。

  • 如果一个类只有静态成员,你可以声明它为"static"

是的,你确实需要锁定你的代码。

 object padlock = new object
 public bool Contains(T item)
 {
    lock (padlock)
    {
        return items.Contains(item);
    }
 }

是。在读取或写入集合之前,需要锁定集合,因为可能会从不同的线程池工作器添加多个用户。您可能也应该计数,尽管如果您不关心100%的准确性,这可能不是一个问题。

根据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()的结果,则此实现可能会使您暴露于死锁。

这段代码本身就不是线程安全的。

我不会对你的"设计"提出任何建议,因为你没有问任何问题。我将假设您找到了使用这些静态成员并像您所做的那样公开列表内容的良好理由。

然而,如果你想让你的代码线程安全,你应该基本上使用一个锁对象来锁定,并用一个锁语句包装你的方法的内容:

    private readonly object syncObject = new object();
    void SomeMethod()
    {
        lock (this.syncObject)
        {
            // Work with your list here
        }
    }

请注意,这些被引发的事件有可能在很长一段时间内持有锁,这取决于委托的操作。在将列表声明为volatile时,可以忽略NoOfOnlineUsers属性中的锁。但是,如果您希望Count值与您在某个时刻使用它的时间一样长,那么也可以在那里使用锁。

正如其他人在这里建议的那样,直接暴露列表,即使使用锁,仍然会对其内容构成"威胁"。我会按照Mark Gravell的建议退回一份副本(这应该符合大多数目的)。

现在,既然你说你在ASP中使用它。. NET环境中,值得一提的是,所有的局部变量和成员变量,以及它们的成员变量(如果有的话)都是线程安全的。