The Light
The Light

Reputation: 27011

How to Create a Thread-Safe Generic List?

I have a Generic List as below

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

I'm using the below methods for it:

.Add
.Find
.FirstOrDefault

The last 2 are LINQ extensions.

I'd need to make this thread-safe to be able to run multiple instances of the container class.

How to achieve that?

Upvotes: 28

Views: 53480

Answers (8)

JasonS
JasonS

Reputation: 7733

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
        {
            lock (_lock) 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)
        {
            return _storage.ToArray().AsEnumerable().GetEnumerator();
        }
    }
    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

Upvotes: 12

Moctar Haiz
Moctar Haiz

Reputation: 187

Never use ConcurrangBag for ordered data. Use Array instead

Upvotes: 1

Max
Max

Reputation: 121

Ok, so I had to completely rewrite my answer. After 2 days of testing I have to say that the JasonS's code has some defects, I guess because of Enumerators. While one thread uses foreach, and the other other changes the list, it throws exceptions.

So I found this answer, and it works for me fine the last 48 hours non-stop, I guess more than 100k threads were created in my application, and used that lists.

The only thing I changed - I've moved entering the locks outside the try-finally section. Read here about the possible exceptions. Also, if you will read MSDN, they have the same approach.

But, as were mentioned in link below, List can not be 100% thread safe, probably that is why there is no default ConcurentList implementation in c#.

Upvotes: 0

Albireo
Albireo

Reputation: 11095

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

Upvotes: 10

Darren
Darren

Reputation: 70728

Use the lock keyword when you manipulate the collection, ie: your Add/Find:

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

Upvotes: 2

JSJ
JSJ

Reputation: 5691

Make your Action as accessible by one only by using lock on any private object

Refer to : Thread Safe Generic Queue Class

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

Upvotes: 0

JaredPar
JaredPar

Reputation: 754715

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>

Upvotes: 42

Tudor
Tudor

Reputation: 62439

You will need to use locks in every place where the collection gets modified or iterated over.

Either that or use one of the new thread-safe data structures, like ConcurrentBag.

Upvotes: 2

Related Questions