线程安全的静态类

时间:2012-10-01 09:00:16

标签: c# multithreading static-methods

我想在c#的windows应用程序中创建自己的事件系统。为此,我写下以下课程:

internal class EventManager
{
    private static List<EventRecord> s_listEvents = new List<EventRecord>();

    public static void AddEvent(EventRecord record)
    {
        record.EventDate = DateTime.Now;
        s_listEvents.Add(record);
    }

    public static List<EventRecord> GetRecordsByDate(DateTime date)
    {
        var r = (from l in s_listEvents
                 where l.EventDate >= date
                 select l).ToList<EventRecord>();
        return r;
    }
}

我想确保EventManager类是线程安全的。因为我要在我的应用程序中同时创建数百个线程。所有线程都很可能使用此类来生成事件。在从不同的线程调用GetRecordsByDate函数时,可以从类外部调用AddEvent函数。

简单地说,您能告诉我这个设计适用于多线程窗口应用程序吗?如果这不是线程安全的,那么我如何使我的类或其成员线程安全?我应该使用同步对象来锁定整个EventManager类,还是应该使用readwritelocker锁定我的s_listEvents静态成员?

5 个答案:

答案 0 :(得分:3)

您应该使用List<T>而不是ConcurrentBag<T>

  

ConcurrentBag是一个线程安全的包实现,针对同一个线程生成和消费存储在包中的数据的场景进行了优化。

更多信息:

http://msdn.microsoft.com/en-us/library/dd381779.aspx

另外,要小心创建要访问的线程数,超过100个线程会使性能降低,因为切换上下文需要时间。

编辑:对于.NET 3.5,您可以使用简单的lock

来创建线程安全的
internal class EventManager
{
    private static List<EventRecord> s_listEvents = new List<EventRecord>();
    private static object _syncObject = new object();


    public static void AddEvent(EventRecord record)
    {
        record.EventDate = DateTime.Now;
        lock(_syncObject)
        {
           s_listEvents.Add(record); 
        }

    }

    public static List<EventRecord> GetRecordsByDate(DateTime date)
    {
        lock (_syncObject)
        {
             var r = (from l in s_listEvents
                 where l.EventDate >= date
                 select l).ToList<EventRecord>();

             return r;
        }

    }
}

修改

根据您的情况,如果您经常读取数据,使用ReaderWriterLockSlimReaderWriterLock对整个应用程序会更好,因为它允许多个线程读取数据。

如果没有,请使用性能更佳的lock

请参阅链接:

http://blogs.msdn.com/b/pedram/archive/2007/10/07/a-performance-comparison-of-readerwriterlockslim-with-readerwriterlock.aspx

答案 1 :(得分:2)

由于类是静态的,因此应该锁定s_listEvents成员。除非你在EventManager本身(或任何其他静态类)上将锁作为静态成员提供,否则调用者很可能无法访问共享锁对象。如果是这种情况,您可以直接在EventManager中实现对s_listEvents的访问锁定。这样就可以避免调用者忘记获取锁定的问题。

读者/作家锁似乎是一个很好的选择。

答案 2 :(得分:2)

您可以使用ReaderWriterLock类:

internal class EventManager
{
    static ReaderWriterLock rwl = new ReaderWriterLock();

    private static List<EventRecord> s_listEvents = new List<EventRecord>();

    public static void AddEvent(EventRecord record)
    {
        record.EventDate = DateTime.Now;
        rwl.AcquireWriterLock(0);
        try
        {
            s_listEvents.Add(record);
        }
        finally
        {
            rwl.ReleaseWriterLock();
        }
    }

    public static List<EventRecord> GetRecordsByDate(DateTime date)
    {
        rwl.AcquireReaderLock(0);
        try
        {
            var r = (from l in s_listEvents
                     where l.EventDate >= date
                     select l).ToList<EventRecord>();
            return r;
        }
        finally
        {
            rwl.ReleaseReaderLock();
        }
    }
}

答案 3 :(得分:1)

以下链接很有用:

How to make a class Thread Safe

private object _lock;

public static void AddEvent(EventRecord record)
{
    lock (_lock)
    {
        record.EventDate = DateTime.Now;
        s_listEvents.Add(record);
    }
}

答案 4 :(得分:1)

您问题的最基本答案如下:要使解决方案保持线程安全,您必须保护数据存储不会同时访问。这是通过将列表锁定在访问它的任何位置来完成的。这意味着当您遍历列表,添加或删除它时,您必须锁定该区域。

即使您正在访问该数量的服务器,您可能也不希望产生100多个线程,而您可能想要使用线程池,有关详细信息,请参阅http://msdn.microsoft.com/en-us/library/0ka9477y(v=vs.90).aspx。这将为您提供一个线程池,用于简单的“签入 - 下载数据 - 检查”任务,就像您正在描述的那样。

编写多线程应用程序时,必须考虑底层存储的使用模式。如果您的应用程序每秒将执行数百次添加,您可能需要考虑拥有基础数据结构的只读副本,每次尝试按日期获取记录时都不会阻止整个系统。有关详细介绍,请参阅Intel's Optimization Guide