Подтвердить что ты не робот

Как создать надежный общий список?

У меня есть общий список, как показано ниже

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

Я использую для него следующие методы:

.Add
.Find
.FirstOrDefault

Последние 2 являются расширениями LINQ.

Мне нужно сделать этот поточно-безопасный, чтобы иметь возможность запускать несколько экземпляров класса контейнера.

Как это сделать?

4b9b3361

Ответ 1

Если это единственные функции, которые вы используете на 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>

Ответ 2

Если вы используете версию 4 или более .NET framework, вы можете использовать потокобезопасные коллекции.

Вы можете заменить 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);
        }
    }
}

Ответ 3

Чтобы расширить ответ на @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();
    }
}

Ответ 4

Вам нужно будет использовать блокировки в каждом месте, где коллекция будет изменена или перезаписана.

Либо это, либо использовать одну из новых потокобезопасных структур данных, например ConcurrentBag.

Ответ 5

Используйте ключевое слово блокировки при манипуляции с коллекцией, то есть: добавьте/найдите:

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

Ответ 6

Сделайте свое действие доступным только одним, используя блокировку для любого частного объекта

Обратитесь к разделу "Класс надежной общей потоковой передачи"

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

Ответ 7

Хорошо, поэтому мне пришлось полностью переписать свой ответ. После 2 дней тестирования я должен сказать, что код JasonS имеет некоторые недостатки, я думаю, из-за Enumerators. В то время как один поток использует foreach, а другой другой изменяет список, он генерирует исключения.

Итак, я нашел этот ответ, и он отлично работает в течение последних 48 часов без остановок, я думаю, в моем приложении было создано более 100 тыс. потоков, и используется список.

Единственное, что я изменил - я перешел в блокировки вне секции try-finally. Прочитайте здесь о возможных исключениях. Кроме того, если вы будете читать MSDN, у них будет тот же подход.

Но, как упоминалось в ссылке ниже, List не может быть на 100% потокобезопасным, возможно, поэтому в С# нет реализации ConcurentList по умолчанию.

Ответ 8

Никогда не используйте ConcurrangBag для упорядоченных данных. Вместо этого используйте Array