我有一个静态List<T>
作为缓存对象,被多个踏板严重读取。我需要每隔5分钟从数据库刷新一次对象。
问题是如果我在其中一个线程使用它时更新了对象,foreach
循环将抛出异常。
我试图实现像inUse = true
和inUpdate = true
这样的标志,以及等待标志设置或释放的而循环,但最终它也变成了很麻烦,我认为有一个错误可以防止对象被更新。
对于我可以使用的这种情况,是否有类似设计模式的东西?
修改
基于Jim Mischel's example,我能够生成以下代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace ConsoleApplication4
{
class Program
{
static Timer g;
static Timer f;
static Timer r;
static Timer l;
static void Main(string[] args)
{
f=new Timer(
o => SetK(new Random().Next(Int32.MinValue, Int32.MaxValue)),
null, 0, 1);
l=new Timer(
o => SetK(new Random().Next(Int32.MinValue, Int32.MaxValue)),
null, 1, 1);
g=new Timer(o => RunLoop(), null, 1000, Timeout.Infinite);
r=new Timer(o => RunLoop(), null, 1001, Timeout.Infinite);
Console.ReadLine();
}
public static void SetK(int g)
{
try {
if(g<0) {
List<int> k=new List<int>(10);
k.Insert(0, g);
k.Insert(1, g);
k.Insert(2, g);
k.Insert(3, g);
k.Insert(4, g);
k.Insert(5, g);
k.Insert(6, g);
k.Insert(7, g);
k.Insert(8, g);
k.Insert(9, g);
SynchronizedCache<Int32>.Set(k);
}
else {
List<int> k=new List<int>(5);
k.Insert(0, g);
k.Insert(1, g);
k.Insert(2, g);
k.Insert(3, g);
k.Insert(4, g);
SynchronizedCache<Int32>.Set(k);
}
}
catch(Exception e) {
}
}
public static void RunLoop()
{
try {
while(true) {
try {
SynchronizedCache<Int32>.GetLock().EnterReadLock();
foreach(var g in SynchronizedCache<Int32>.Get()) {
Console.Clear();
Console.WriteLine(g);
}
}
finally {
SynchronizedCache<Int32>.GetLock().ExitReadLock();
}
}
}
catch(Exception e) {
}
}
}
public static class SynchronizedCache<T>
{
private static ReaderWriterLockSlim
cacheLock=new ReaderWriterLockSlim();
private static List<T> cache=new List<T>();
public static ReaderWriterLockSlim GetLock()
{
return cacheLock;
}
public static void Set(List<T> list)
{
cacheLock.EnterWriteLock();
try {
cache=list;
}
finally {
cacheLock.ExitWriteLock();
}
}
public static List<T> Get()
{
return cache;
}
}
}
答案 0 :(得分:7)
不幸的是,System.Collections.Concurrent
集合没有很好地映射到List<T>
。在我需要这样的东西的情况下,我使用一个内部使用ReaderWriterLockSlim
来保护它的包装器。例如:
public class ConcurrentList<T>: IList<T>
{
private readonly List<T> _theList;
private readonly ReaderWriterLockSlim _rwlock = new ReaderWriterLockSlim();
public ConcurrentList()
{
_theList = new List<T>();
}
public ConcurrentList(IEnumerable<T> collection)
{
_theList = new List<T>(collection);
}
public ConcurrentList(int size)
{
_theList = new List<T>(size);
}
public int IndexOf(T item)
{
_rwlock.EnterReadLock();
try
{
return _theList.IndexOf(item);
}
finally
{
_rwlock.ExitReadLock();
}
}
public void Insert(int index, T item)
{
_rwlock.EnterWriteLock();
try
{
_theList.Insert(index, item);
}
finally
{
_rwlock.ExitWriteLock();
}
}
public T this[int index]
{
get
{
_rwlock.EnterReadLock();
try
{
return _theList[index];
}
finally
{
_rwlock.ExitReadLock();
}
}
set
{
_rwlock.EnterWriteLock();
try
{
_theList[index] = value;
}
finally
{
_rwlock.ExitWriteLock();
}
}
}
public IEnumerator<T> GetEnumerator()
{
_rwlock.EnterReadLock();
try
{
foreach (var item in _theList)
{
yield return item;
}
}
finally
{
_rwlock.ExitReadLock();
}
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
// other methods not implemented, for brevity
}
}
这是第一次设置它的一点工作,但在那之后效果很好。它支持任意数量的并发读者,或者只支持一个作者。
任意数量的读者可以同时访问该列表。如果线程想要写入列表,则必须获取写锁定。为了获得写锁定,它等待所有读者完成。一旦线程请求写锁定,任何线程都不能进入读锁定。一旦作家完成,读者将被允许通过。
@Servy也有一个很好的建议。如果您的线程只是在阅读,并且定期从数据库中刷新列表(即完全构建一个新列表),那么就像他说的那样很容易做到。
答案 1 :(得分:4)
使用System.Collections.Concurrent
命名空间中的一个并发集合。
System.Collections.Concurrent
命名空间提供了几个线程安全的集合类,只要多个线程同时访问集合,就应该使用它们代替System.Collections
和System.Collections.Generic
命名空间中的相应类型。
答案 2 :(得分:4)
在这种情况下,最好不要改变单个列表,而是创建一个包含所需更改的新列表(即包含其他项目,不添加某些项目等)。 )然后只需设置缓存值即可引用新列表。由于设置缓存的值是原子的,因此确保一旦有人从缓存中获取值,它可能会有点陈旧,但他们仍然可以正常读取它。
答案 3 :(得分:0)
我认为最好的解决方案是使用互斥设计模式来避免竞争条件。幸运的是,.NET框架具有互斥设计模式的标准实现;您可以阅读http://msdn.microsoft.com/it-it/library/system.threading.mutex.aspx上的文档。