2012-04-03 18 views
21

Ho un elenco generico, come di seguitoCome creare un elenco generico sicuro per il thread?

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

sto usando metodi indicati per esso:

.Add 
.Find 
.FirstOrDefault 

Gli ultimi 2 sono estensioni LINQ.

Avrei bisogno di rendere questo thread sicuro per poter eseguire più istanze della classe contenitore.

Come raggiungerlo?

+0

Per essere chiari, si desidera utilizzare ogni istanza da più thread contemporaneamente? Più thread muteranno la lista? –

+0

Date un'occhiata a concorrenza: http://geekswithblogs.net/BlackRabbitCoder/archive/2011/02/10/c.net-little-wonders-the-concurrent-collections-1-of-3.aspx – Silvermind

risposta

41

Se quelle sono le uniche funzioni che si sta utilizzando sulla List<T> quindi il modo più semplice è quello di scrivere un rapido wrapper che sincronizza l'accesso 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(); 
    } 
    } 
} 

Consiglio vivamente l'approccio di un nuovo tipo + serratura privato oggetto. Rende molto più ovvio al prossimo ragazzo che eredita il tuo codice quale fosse l'intento effettivo.

Si noti inoltre che .Net 4.0 ha introdotto una nuova serie di raccolte specificamente destinate a essere utilizzate da più thread. Se uno di questi soddisfa le tue esigenze, ti consiglio vivamente di utilizzarlo per il tuo.

  • ConcurrentStack<T>
  • ConcurrentQueue<T>
+7

+1 per pensando al prossimo ragazzo! – jp2code

+1

Penso che sia meglio utilizzare un ReaderWriterLock in uno scenario di questo tipo, per consentire a più thread di leggere la raccolta contemporaneamente. – Hari

+1

@ dipende dipende. Un 'ReaderWriterLock' è solo più efficiente se le letture sono almeno il doppio delle scritture, altrimenti un vecchio blocco semplice è più veloce. L'OP dovrebbe fornire il contesto per dire che questo era o non era il caso – JaredPar

1

È necessario utilizzare i blocchi in ogni posizione in cui la raccolta viene modificata o iterata.

Oppure utilizzare una delle nuove strutture dati thread-safe, ad esempio ConcurrentBag.

0

impegnati durante tutta la più accessibile da un solo mediante blocco su qualsiasi oggetto privato

Fare riferimento a: thread-safe coda Generico Classe

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

+0

L'allegato .zip non è disponibile in questo articolo. Peccato, volevo un elenco sicuro da thread <>. –

+0

può vedere la mia risposta sopra per un'implementazione thread-safe – JasonS

1

utilizzare la parola chiave di blocco quando si manipola la raccolta, vale a dire: il tuo Add/Trova:

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

che non è un ottimo approccio in quanto può portare facilmente a deadlock. –

7

Se si sta utilizzando la versione 4 o superiore del framework .NET è possibile utilizzare il thread-safe collections.

È possibile sostituire 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); 
     } 
    } 
} 
+2

Esistono reson per non utilizzare la classe SynchronizedCollection? –

+4

Una borsa ha un comportamento diverso da una lista per quanto riguarda l'ordine. – aggsol

+0

La raccolta sincronizzata è obsoleta – denfromufa

7

di espandere @ di JaradPar risposta, qui è un'implementazione completa con alcune caratteristiche extra, come descritto nel sommario

/// <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(); 
    } 
} 
+0

FYI: se hai bisogno di più prestazioni di lettura simultanee, potresti usare ReadWriteLockSlim invece del blocco. ma prima di preoccuparti, misura e assicurati che questo sia il collo di bottiglia delle tue prestazioni! – JasonS

+0

Solo una domanda ... Perché il doppio blocco in IEnumerator pubblico GetEnumerator()? Grazie. –

+0

Grazie per aver postato questo. – joelc

0

Ok, quindi ho dovuto riscrivere completamente la mia risposta. Dopo 2 giorni di test devo dire che il codice di JasonS ha alcuni difetti, credo a causa degli Enumeratori. Mentre un thread utilizza foreach e l'altro modifica l'elenco, genera eccezioni.

Così ho trovato this answer, e funziona bene per me delle ultime 48 ore non-stop, credo che più di 100k discussioni sono stati creati nella mia richiesta, e usato che le liste.

L'unica cosa che ho cambiato - ho spostato inserendo le serrature di fuori della sezione try-finally. Read here sulle possibili eccezioni. Inoltre, se leggerai MSDN, hanno lo stesso approccio.

Ma, come sono stati menzionati nel link qui sotto, elenco non può essere al 100% thread-safe, probabilmente è per questo che non v'è alcuna implementazione ConcurentList di default in C#.

0

Non utilizzare mai ConcurrangBag per i dati ordinati. Usa matrice invece