3

我有一个静态List<T>作为缓存对象,被多个线程大量读取。我需要每 5 分钟从数据库刷新一次对象。

问题是,如果我在其中一个线程使用对象时更新对象,则foreach循环将引发异常。

我试图实现像inUse = trueand之类的标志inUpdate = true,以及等待标志设置或释放的while循环,但最终它变得太麻烦了,我认为有一个错误会阻止对象被更新。

对于这种情况,我可以使用类似设计模式的东西吗?


编辑:

基于Jim Mischel 的示例,我能够生成以下代码:

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;
        }
    }
}
4

4 回答 4

7

不幸的是,没有一个System.Collections.Concurrent集合可以很好地映射到List<T>. 如果我需要这样的东西,我会使用一个在内部使用 aReaderWriterLockSlim来保护它的包装器。例如:

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 也有一个很好的建议。如果您的线程只是在阅读,并且您定期从数据库中刷新列表(即完全构建一个新列表),那么按照他的说法很容易做到。

于 2013-03-21T21:32:27.777 回答
4

使用System.Collections.Concurrent命名空间中的并发集合之一。

命名空间提供了几个线程安全的集合类,当多个线程同时访问集合时System.Collections.Concurrent,应该使用这些类来代替 和 命名空间中的相应类型System.CollectionsSystem.Collections.Generic

于 2013-03-21T21:21:01.400 回答
4

与其改变单个列表,在这种情况下,最好创建一个包含您想要的更改的新列表(即具有其他项目,不添加某些项目等),然后将缓存值设置为请参阅新列表。由于设置缓存的值是原子的,因此您可以确保一旦有人从缓存中获取值,它可能会有点陈旧,但他们仍然能够很好地读取它。

于 2013-03-21T21:35:31.557 回答
0

我认为最好的解决方案是使用互斥锁设计模式来避免竞争条件。幸运的是,.NET 框架具有互斥体设计模式的标准实现。您可以阅读http://msdn.microsoft.com/it-it/library/system.threading.mutex.aspx上的文档。

于 2013-03-21T21:49:45.220 回答