在.NET中并发访问静态成员

时间:2011-07-17 10:27:40

标签: c# .net multithreading concurrency locking

我有一个包含静态集合的类,用于将登录用户存储在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);
        }
    }
}

6 个答案:

答案 0 :(得分:13)

这绝对不是线程安全的。任何时候2个线程正在做某事(在Web应用程序中很常见),混乱是可能的 - 异常或静默数据丢失。

是的,您需要某种同步,例如lock;而static对于数据存储IMO来说通常是一个非常糟糕的主意(除非非常小心地对待并限于配置数据之类的东西)。

此外 - static事件因使对象图意外存活的好方法而臭名昭着。也要谨慎对待;如果您只订阅一次,那么很好 - 但不要按订阅订阅等。

此外 - 它不仅仅是锁定操作,因为这一行:

return onlineUsers;

返回您的列表,现在不受保护。必须同步 所有 对项目的访问权限。就个人而言,我会返回副本,即

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

最后,从此处返回.Count可能会造成混淆 - 因为无论如何都不能保证Count。它仅在那个时间点是信息

答案 1 :(得分:4)

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

一些注意事项:

  • 使用HashSet<string>代替List<string>可能是一个好主意,因为对于这样的操作(ContainsRemove来说效率要高得多)。但这并没有改变锁定要求的任何内容。

  • 如果某个类只有静态成员,则可以将其声明为“静态”。

答案 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环境中使用它,那么值得一提的是,所有本地变量和成员变量以及它们的成员变量(如果有的话)都是线程安全的。