una - thread invoke c#
¿Cómo crear una lista genérica segura para subprocesos? (8)
Tengo una lista genérica como abajo
public static readonly List<Customer> Customers = new List<Customer>();
Estoy usando los siguientes métodos para ello:
.Add
.Find
.FirstOrDefault
Los 2 últimos son extensiones de LINQ.
Tendría que hacer que este hilo sea seguro para poder ejecutar varias instancias de la clase contenedora.
¿Cómo lograr eso?
Deberá usar bloqueos en todos los lugares donde la colección se modifique o se repita.
O eso o utilizar una de las nuevas estructuras de datos seguras para subprocesos, como ConcurrentBag .
Haga que su acción sea accesible solo con una cerradura en cualquier objeto privado
Consulte: Thread Safe Generic Queue Class
http://www.codeproject.com/Articles/38908/Thread-Safe-Generic-Queue-Class
Nunca utilice ConcurrangBag para los datos solicitados. Utilice Array en su lugar
Ok, entonces tuve que reescribir completamente mi respuesta. Después de 2 días de pruebas, tengo que decir que el código de JasonS tiene algunos defectos, supongo que debido a los enumeradores. Mientras que un hilo usa foreach, y el otro cambia la lista, arroja excepciones.
Así que encontré esta respuesta , y me funciona bien en las últimas 48 horas sin parar, supongo que se crearon más de 100k hilos en mi aplicación y usé esas listas.
Lo único que cambié: me mudé ingresando a los bloqueos fuera de la sección de prueba-finalmente. Lea aquí sobre las posibles excepciones. Además, si va a leer MSDN, tienen el mismo enfoque.
Pero, como se mencionó en el enlace a continuación, la Lista no puede ser 100% segura para subprocesos, probablemente es por eso que no hay una implementación ConcurentList predeterminada en c #.
Para ampliar la respuesta de @JaradPar, aquí hay una implementación completa con algunas características adicionales, como se describe en el resumen.
/// <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();
}
}
Si esas son las únicas funciones que está utilizando en la List<T>
, la forma más sencilla es escribir un contenedor rápido que sincronice el acceso con un 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();
}
}
}
Recomiendo encarecidamente el enfoque de un nuevo tipo + objeto de bloqueo privado. Lo hace mucho más obvio para la próxima persona que herede su código cuál era la intención real.
También tenga en cuenta que .Net 4.0 introdujo un nuevo conjunto de colecciones destinadas específicamente a ser utilizadas desde múltiples subprocesos. Si alguno de estos satisface sus necesidades, le recomiendo que lo utilice sobre rodar el suyo.
-
ConcurrentStack<T>
-
ConcurrentQueue<T>
Si está utilizando la versión 4 o superior de .NET framework, puede usar las colecciones seguras para subprocesos .
Puede reemplazar la List<T>
con 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);
}
}
}
Use la palabra clave de bloqueo cuando manipule la colección, es decir: su Agregar / Buscar:
lock(Customers) {
Customers.Add(new Customer());
}