3

List<T>複数のスレッドによって頻繁に読み取られるキャッシュ オブジェクトとしてスタティックがあります。データベースから 5 分ごとにオブジェクトを更新する必要があります。

問題は、オブジェクトがスレッドの 1 つによって使用されている間にオブジェクトを更新すると、foreachループが例外をスローすることです。

inUse = trueや などのフラグとinUpdate = true、フラグが設定または解放されるのを待つwhileループを実装しようとしましたが、最終的には面倒になり、オブジェクトがまったく更新されないバグがあると思います。

このような場合に使えるデザインパターンのようなものはありますか?


編集:

Jim Mischel の 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;
        }
    }
}
4

4 に答える 4

7

残念ながら、 にSystem.Collections.Concurrentうまく対応するコレクションはありませんList<T>。そのようなことが必要な場合は、内部で a を使用し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
}

}

初めて設定するのは少し手間がかかりますが、その後はうまく機能します。任意の数の同時リーダー、または 1 つのライターのみをサポートします。

任意の数のリーダーがリストに同時にアクセスできます。スレッドがリストに書き込みたい場合は、書き込みロックを取得する必要があります。書き込みロックを取得するために、すべての読み取りが完了するまで待機します。スレッドが書き込みロックを要求すると、どのスレッドも読み取りロックに入ることはできません。ライターが終了すると、リーダーは許可されます。

@Servy にも良い提案があります。スレッドが読み取りだけで、定期的にデータベースからリストを更新する (つまり、新しいリストを完全に作成する) 場合、彼の言うように実行するのは非常に簡単です。

于 2013-03-21T21:32:27.777 に答える
4

この場合、単一のリストを変更するのではなく、必要な変更を組み込んだ新しいリストを作成して(つまり、追加のアイテムがある、いくつかのアイテムを追加しないなど)、キャッシュ値を次のように設定する方がよいでしょう。新しいリストを参照してください。キャッシュの値の設定はアトミックであるため、誰かがキャッシュから値を取得すると、それは少し古くなっている可能性がありますが、それでも問題なく読み取ることができます。

于 2013-03-21T21:35:31.557 に答える
4

System.Collections.Concurrent名前空間からの並行コレクションの 1 つを使用します。

名前空間は、複数のスレッドが同時にコレクションにアクセスする場合に、および名前空間System.Collections.Concurrent内の対応する型の代わりに使用する必要があるスレッド セーフなコレクション クラスをいくつか提供します。System.CollectionsSystem.Collections.Generic

于 2013-03-21T21:21:01.400 に答える
0

最適な解決策は、ミューテックス デザイン パターンを使用して競合状態を回避することだと思います。さいわい、.NET フレームワークにはミューテックス設計パターンの標準実装があります。http://msdn.microsoft.com/it-it/library/system.threading.mutex.aspxでドキュメントを読むことができます。

于 2013-03-21T21:49:45.220 に答える