How to Create a Thread-Safe Generic List?
If those are the only functions you are using on List<T>
then the easiest way is to write a quick wrapper that synchronizes access with a 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(); } }}
I highly recommend the approach of a new type + private lock object. It makes it much more obvious to the next guy who inherits your code what the actual intent was.
Also note that .Net 4.0 introduced a new set of collections specifically aimed at being used from multiple threads. If one of these meets your needs I'd highly recommend using it over rolling your own.
ConcurrentStack<T>
ConcurrentQueue<T>
If you're using version 4 or greater of the .NET framework you can use the thread-safe collections.
You can replace List<T>
with 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); } }}
To expand on @JaradPar's answer, here is a full implementation with a few extra features, as described in the summary
/// <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(); }}