web-dev-qa-db-ja.com

スレッドセーフList <T>プロパティ

間違いなくスレッドセーフで使用できるプロパティとしてList<T>の実装が必要です。

このようなもの:

private List<T> _list;

private List<T> MyT
{
    get { // return a copy of _list; }
    set { _list = value; }
}

まだコレクションのコピー(複製)を返す必要があるようですので、どこかでコレクションを反復し、同時にコレクションが設定されている場合、例外は発生しません。

スレッドセーフコレクションプロパティを実装する方法は?

104
Xaqron

.Net 4をターゲットにしている場合、 System.Collections.Concurrent Namespaceにいくつかのオプションがあります

この場合、ConcurrentBag<T>の代わりにList<T>を使用できます

168
Bala R

投票数が最も多くても、通常、System.Collections.Concurrent.ConcurrentBag<T>のスレッドセーフな代替としてSystem.Collections.Generic.List<T>をそのまま使用することはできません(RadekStromskýが既に指摘している)。

しかし、System.Collections.Generic.SynchronizedCollection<T>と呼ばれるクラスがあります。これは、フレームワークの.NET 3.0の部分から既に存在していますが、ほとんど知られていないと思われる場所に隠されています。それ以上(少なくとも私はしませんでした)。

SynchronizedCollection<T>は、アセンブリにコンパイルされますSystem.ServiceModel.dll(クライアントプロファイルの一部ですが、ポータブルクラスライブラリの一部ではありません)。

それが役に立てば幸いです。

80
Christoph

サンプルのThreadSafeListクラスを作成するのは簡単だと思います。

public class ThreadSafeList<T> : IList<T>
{
    protected List<T> _interalList = new List<T>();

    // Other Elements of IList implementation

    public IEnumerator<T> GetEnumerator()
    {
        return Clone().GetEnumerator();
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return Clone().GetEnumerator();
    }

    protected static object _lock = new object();

    public List<T> Clone()
    {
        List<T> newList = new List<T>();

        lock (_lock)
        {
            _interalList.ForEach(x => newList.Add(x));
        }

        return newList;
    }
}

列挙子を要求する前にリストを複製するだけなので、列挙は実行中に変更できないコピーで機能します。

16
Tejs

受け入れられた答えもConcurrentBagであり、答えに対するRadekのコメントは次のように言っています。 「。

したがって、.NET 4.0以降を使用する場合、回避策として、整数TKeyを配列インデックスとして、TValueを配列値としてConcurrentDictionaryを使用することができます。これは、Pluralsightの C#Concurrent Collectionsコース のリストを置き換える推奨方法です。 ConcurrentDictionaryは、上記の両方の問題を解決します。インデックスへのアクセスと順序付け(内部のハッシュテーブルなので順序付けに依存することはできませんが、現在の.NET実装は要素の追加順序を保存します)。

5
tytyryty

次を使用できます。

var threadSafeArrayList = ArrayList.Synchronized(new ArrayList());

スレッドセーフArrayListを作成する方法

4
Hani Nakhli

Tのリストのソースコード( https://referencesource.Microsoft.com/#mscorlib/system/collections/generic/list.cs,c66df6f36c131877 )を見ると、 TのSynchronizedListと呼ばれるクラス(もちろん内部-なぜ、マイクロソフト、なぜ?!?!)です。ここにコードをコピーして貼り付けます。

   [Serializable()]
    internal class SynchronizedList : IList<T> {
        private List<T> _list;
        private Object _root;

        internal SynchronizedList(List<T> list) {
            _list = list;
            _root = ((System.Collections.ICollection)list).SyncRoot;
        }

        public int Count {
            get {
                lock (_root) { 
                    return _list.Count; 
                }
            }
        }

        public bool IsReadOnly {
            get {
                return ((ICollection<T>)_list).IsReadOnly;
            }
        }

        public void Add(T item) {
            lock (_root) { 
                _list.Add(item); 
            }
        }

        public void Clear() {
            lock (_root) { 
                _list.Clear(); 
            }
        }

        public bool Contains(T item) {
            lock (_root) { 
                return _list.Contains(item);
            }
        }

        public void CopyTo(T[] array, int arrayIndex) {
            lock (_root) { 
                _list.CopyTo(array, arrayIndex);
            }
        }

        public bool Remove(T item) {
            lock (_root) { 
                return _list.Remove(item);
            }
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
            lock (_root) { 
                return _list.GetEnumerator();
            }
        }

        IEnumerator<T> IEnumerable<T>.GetEnumerator() {
            lock (_root) { 
                return ((IEnumerable<T>)_list).GetEnumerator();
            }
        }

        public T this[int index] {
            get {
                lock(_root) {
                    return _list[index];
                }
            }
            set {
                lock(_root) {
                    _list[index] = value;
                }
            }
        }

        public int IndexOf(T item) {
            lock (_root) {
                return _list.IndexOf(item);
            }
        }

        public void Insert(int index, T item) {
            lock (_root) {
                _list.Insert(index, item);
            }
        }

        public void RemoveAt(int index) {
            lock (_root) {
                _list.RemoveAt(index);
            }
        }
    }

個人的には、 SemaphoreSlim を使用してより良い実装を作成できることを知っていたと思いますが、それに到達しませんでした。

3

より原始的なものを使用することもできます

Monitor.Enter(lock);
Monitor.Exit(lock);

どのロックが使用するか(この投稿を参照 C#ロックブロックで再割り当てされるオブジェクトをロックする )。

コードで例外を予期している場合、これは安全ではありませんが、次のようなことができます。

using System;
using System.Collections.Generic;
using System.Threading;
using System.Linq;

public class Something
{
    private readonly object _lock;
    private readonly List<string> _contents;

    public Something()
    {
        _lock = new object();

        _contents = new List<string>();
    }

    public Modifier StartModifying()
    {
        return new Modifier(this);
    }

    public class Modifier : IDisposable
    {
        private readonly Something _thing;

        public Modifier(Something thing)
        {
            _thing = thing;

            Monitor.Enter(Lock);
        }

        public void OneOfLotsOfDifferentOperations(string input)
        {
            DoSomethingWith(input);
        }

        private void DoSomethingWith(string input)
        {
            Contents.Add(input);
        }

        private List<string> Contents
        {
            get { return _thing._contents; }
        }

        private object Lock
        {
            get { return _thing._lock; }
        }

        public void Dispose()
        {
            Monitor.Exit(Lock);
        }
    }
}

public class Caller
{
    public void Use(Something thing)
    {
        using (var modifier = thing.StartModifying())
        {
            modifier.OneOfLotsOfDifferentOperations("A");
            modifier.OneOfLotsOfDifferentOperations("B");

            modifier.OneOfLotsOfDifferentOperations("A");
            modifier.OneOfLotsOfDifferentOperations("A");
            modifier.OneOfLotsOfDifferentOperations("A");
        }
    }
}

これに関する素晴らしい点の1つは、一連の操作の期間中にロックを取得することです(各操作でロックするのではなく)。これは、出力が適切なチャンクで出力されることを意味します(これを使用すると、外部プロセスから画面に出力が表示されます)

クラッシュを止めるために重要なことをするThreadSafeListのシンプルさと透明性が本当に好きです。

2
JonnyRaa

_list.ToList()がコピーを作成すると信じています。次のような必要がある場合は、クエリすることもできます。

_list.Select("query here").ToList(); 

とにかく、msdnはこれが実際にコピーであり、単なる参照ではないと言います。ああ、はい、他の人が指摘したように、setメソッドをロックする必要があります。

1
Jonathan Henson

これを見つける人の多くは、スレッドセーフなインデックス付きの動的なサイズのコレクションを望んでいるようです。私が知っている最も近くて簡単なことはそうだろう。

System.Collections.Concurrent.ConcurrentDictionary<int, YourDataType>

これにより、通常のインデックス作成動作が必要な場合は、キーが適切に侵害されていることを確認する必要があります。注意する場合は、追加する新しいキーと値のペアのキーとして.countで十分です。

1
user2163234

マルチスレッドシナリオでList<T>を扱う人には、 Immutable Collections 特に ImmutableArray を参照することをお勧めします。

あなたが持っているとき、私はそれが非常に有用であることがわかりました:

  1. リスト内の比較的少数のアイテム
  2. それほど多くない読み取り/書き込み操作
  3. 多数の同時アクセス(読み取りモードでリストにアクセスする多くのスレッド)

また、何らかのトランザクションのような動作を実装する必要がある場合にも役立ちます(つまり、失敗した場合に挿入/更新/削除操作を元に戻す)

0
adospace