web-dev-qa-db-ja.com

スレッドセーフジェネリックリストを作成する方法

私は次のような一般的なリストを持っています

public static readonly List<Customer> Customers = new List<Customer>();

私はそれのために以下の方法を使用しています:

.Add
.Find
.FirstOrDefault

最後の2つはLINQ拡張機能です。

コンテナクラスの複数のインスタンスを実行できるようにするには、このスレッドセーフにする必要があります。

それを達成する方法は?

23
The Light

これらがList<T>で使用している唯一の関数である場合、最も簡単な方法は、アクセスをlockと同期するクイックラッパーを記述することです。

class MyList<T> { 
  private List<T> _list = new List<T>();
  private object _sync = new object();
  public void Add(T value) {
    lock (_sync) {
      _list.Add(value);
    }
  }
  public bool Find(Predicate<T> predicate) {
    lock (_sync) {
      return _list.Find(predicate);
    }
  }
  public T FirstOrDefault() {
    lock (_sync) {
      return _list.FirstOrDefault();
    }
  }
}

新しいタイプ+プライベートロックオブジェクトのアプローチを強くお勧めします。実際の意図が何であるかをあなたのコードを継承する次の人にとって、それははるかに明白にします。

また、.Net 4.0では、複数のスレッドからの使用を特に目的とした新しいコレクションのセットが導入されたことに注意してください。これらのいずれかがあなたのニーズを満たしている場合、私はあなた自身のものを転がすよりもそれを使用することを強くお勧めします。

  • ConcurrentStack<T>
  • ConcurrentQueue<T>
42
JaredPar

バージョン4以降の.NET Frameworkを使用している場合は、 thread-safe collections を使用できます。

List<T>ConcurrentBag<T>に置き換えることができます:

namespace Playground.Sandbox
{
    using System.Collections.Concurrent;
    using System.Threading.Tasks;

    public static class Program
    {
        public static void Main()
        {
            var items = new[] { "Foo", "Bar", "Baz" };
            var bag = new ConcurrentBag<string>();
            Parallel.ForEach(items, bag.Add);
        }
    }
}
10
Albireo

@JaradParの答えを拡張するために、要約で説明されているように、いくつかの追加機能を備えた完全な実装を示します

    /// <summary>
/// a thread-safe list with support for:
/// 1) negative indexes (read from end).  "myList[-1]" gets the last value
/// 2) modification while enumerating: enumerates a copy of the collection.
/// </summary>
/// <typeparam name="TValue"></typeparam>
public class ConcurrentList<TValue> : IList<TValue>
{
    private object _lock = new object();
    private List<TValue> _storage = new List<TValue>();
    /// <summary>
    /// support for negative indexes (read from end).  "myList[-1]" gets the last value
    /// </summary>
    /// <param name="index"></param>
    /// <returns></returns>
    public TValue this[int index]
    {
        get
        {
            lock (_lock)
            {
                if (index < 0)
                {
                    index = this.Count - index;
                }
                return _storage[index];
            }
        }
        set
        {
            lock (_lock)
            {
                if (index < 0)
                {
                    index = this.Count - index;
                }
                _storage[index] = value;
            }
        }
    }

    public void Sort()
    {
        lock (_lock)
        {
            _storage.Sort();
        }
    }

    public int Count
    {
        get
        {
            return _storage.Count;
        }
    }

    bool ICollection<TValue>.IsReadOnly
    {
        get
        {
            return ((IList<TValue>)_storage).IsReadOnly;
        }
    }

    public void Add(TValue item)
    {
        lock (_lock)
        {
            _storage.Add(item);
        }
    }

    public void Clear()
    {
        lock (_lock)
        {
            _storage.Clear();
        }
    }

    public bool Contains(TValue item)
    {
        lock (_lock)
        {
            return _storage.Contains(item);
        }
    }

    public void CopyTo(TValue[] array, int arrayIndex)
    {
        lock (_lock)
        {
            _storage.CopyTo(array, arrayIndex);
        }
    }


    public int IndexOf(TValue item)
    {
        lock (_lock)
        {
            return _storage.IndexOf(item);
        }
    }

    public void Insert(int index, TValue item)
    {
        lock (_lock)
        {
            _storage.Insert(index, item);
        }
    }

    public bool Remove(TValue item)
    {
        lock (_lock)
        {
            return _storage.Remove(item);
        }
    }

    public void RemoveAt(int index)
    {
        lock (_lock)
        {
            _storage.RemoveAt(index);
        }
    }

    public IEnumerator<TValue> GetEnumerator()
    {

        lock (_lock)
        {
            lock (_lock)
            {
                return (IEnumerator<TValue>)_storage.ToArray().GetEnumerator();
            }
        }
    }
    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}
7
JasonS

コレクションが変更または反復されるすべての場所でロックを使用する必要があります。

それか、または ConcurrentBag のような新しいスレッドセーフデータ構造のいずれかを使用します。

2
Tudor

コレクションを操作するときは、lockキーワードを使用します。つまり、Add/Find:

lock(Customers) {
    Customers.Add(new Customer());
}
1
Darren

順序付けされたデータにはConcurrangBagを使用しないでください。代わりに配列を使用してください

0
Moctar Haiz

プライベートオブジェクトのロックを使用して、アクションを1人だけがアクセスできるようにします

参照:スレッドセーフジェネリックキュークラス

http://www.codeproject.com/Articles/38908/Thread-Safe-Generic-Queue-Class

0
JSJ

わかりましたので、答えを完全に書き直さなければなりませんでした。 2日間のテストの後、私はJasonSのコードにいくつかの欠陥があると言わざるを得ません。それは列挙子のためだと思います。 1つのスレッドがforeachを使用し、他のスレッドがリストを変更している間、例外をスローします。

だから私は この答え を見つけました、そしてそれは私にとって最後の48時間ノンストップでうまくいきます、私は私のアプリケーションで10万以上のスレッドが作成され、そのリストを使用したと思います。

私が変更した唯一のこと-私は、try-finallyセクションの外側のロックに入ることに移動しました。 こちらをご覧ください 考えられる例外について。また、MSDNを読む場合は、同じアプローチを採用しています。

ただし、以下のリンクで言及したように、Listは100%スレッドセーフにすることはできません。おそらく、c#に既定のConcurentList実装がない理由です。

0
Max