我想在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
静态成员?
答案 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;
}
}
}
修改:
根据您的情况,如果您经常读取数据,使用ReaderWriterLockSlim
和ReaderWriterLock
对整个应用程序会更好,因为它允许多个线程读取数据。
如果没有,请使用性能更佳的lock
。
请参阅链接:
答案 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