2013-02-18 2 views
7

Mentre la risposta a this question è eccellente, implica che è necessario circondare le chiamate a List.ToArray() in un blocco per la concorrenza. this blog post implica anche che potrebbe fallire in modo catastrofico (ma raramente). Generalmente utilizzo ToArray piuttosto che un lock in quando si enumerano elenchi o altre raccolte per evitare l'eccezione "Collection Modified, Enumeration may not complete". Questa risposta e il post sul blog hanno messo in discussione questa ipotesi.Can ToArray() può generare un'eccezione?

La documentazione per List.ToArray() non elenca eccezioni, quindi ho sempre presupposto che verrà sempre completata (anche se forse con dati obsoleti) e che mentre non è thread-safe dal punto di vista della consistenza dei dati , è thread-safe dal punto di vista dell'esecuzione del codice: in altre parole, non genera un'eccezione e chiamarla non corrompe la struttura interna dei dati della raccolta sottostante.

Se questa ipotesi non è corretta, anche se non ha mai causato un problema, potrebbe essere una bomba a tempo in un'applicazione ad alta disponibilità. Qual è la risposta definitiva?

+0

Elenco .Add inoltre non genera un'eccezione se altri thread modifica l'elenco contemporaneamente. Non è ancora sicuro per i thread. Controlla solo che non stai modificando e enumerandolo contemporaneamente nella stessa discussione. Cosa ti fa pensare che un metodo che non è documentato per essere thread-safe potrebbe essere thread-safe? (Supponendo che si sta parlando di Lista .ToArray o Enumerable.ToArray. ConcurrentBag .ToArray è thread-safe, come enumerare un ConcurrentBag senza ToArray.) – dtb

+0

ho ristretto la portata della questione di concentrarsi sulla lista e lista poiché questi sono quelli di cui sono più preoccupato. Ho visto questa tecnica utilizzata in un certo numero di framework open source popolari, quindi non credo di essere l'unico a fare ipotesi sulla "sicurezza" della tecnica. Non sto chiedendo se sono thread-safe (per definizione non lo sono), ma dovrei lanciare nel trovare e correggere istanze di chiamate "non sicure" a ToArray()? –

+0

Sei sicuro che questi framework open source utilizzino ToArray piuttosto che un blocco o utilizzino ToArray per modificare un elenco mentre lo enumerano nello stesso thread? – dtb

risposta

5

Non troverete documentazione sulle possibili eccezioni del metodo ToArray per un semplice motivo. Questo è un metodo di estensione che ha molti 'sovraccarichi'. Hanno tutti la stessa firma del metodo, ma l'implementazione è diversa per diversi tipi di raccolta, ad es. List<T> e HashSet<T>.

Tuttavia, possiamo fare un presupposto sicuro per la maggior parte del codice che .NET Framework BCL non esegue alcun blocco per motivi di prestazioni. Ho anche verificato l'implementazione molto specifica di ToList per List<T>.

public T[] ToArray() 
{ 
    T[] array = new T[this._size]; 
    Array.Copy(this._items, 0, array, 0, this._size); 
    return array; 
} 

Come si potrebbe immaginare, è abbastanza semplice codice che finisce esecuzione in mscorlib. Per questa specifica implementazione, è anche possibile visualizzare le eccezioni che potrebbero verificarsi in MSDN page for Array.Copy method. Si riduce ad un'eccezione che viene generata se il rango dell'elenco cambia subito dopo che l'array di destinazione è stato appena assegnato.

Tenendo presente che List<T> è un esempio banale, è possibile immaginare che le possibilità di eccezioni aumentino nelle strutture che richiedono un codice più complicato per poter essere archiviate in un array. Implementazione per Queue<T> è un candidato che è più probabilità di fallire:

public T[] ToArray() 
{ 
    T[] array = new T[this._size]; 
    if (this._size == 0) 
    { 
     return array; 
    } 
    if (this._head < this._tail) 
    { 
     Array.Copy(this._array, this._head, array, 0, this._size); 
    } 
    else 
    { 
     Array.Copy(this._array, this._head, array, 0, this._array.Length - this._head); 
     Array.Copy(this._array, 0, array, this._array.Length - this._head, this._tail); 
    } 
    return array; 
} 
+0

Grande analisi. Questo risponde alla domanda in modo definitivo. Grazie! –

0

Prima di tutto è necessario chiarire che il callsite deve essere in una regione thread-safe. La maggior parte delle regioni nel tuo codice non saranno regioni di tipo "thread" e assumeranno un singolo thread di esecuzione in un dato momento (per la maggior parte del codice dell'applicazione). Per (una stima molto approssimativa) il 99% di tutti i codici di applicazione questa domanda non ha senso.

In secondo luogo dovrete chiarire "cosa" è realmente la funzione di enumerazione, poiché questo varierà sul tipo di enumerazione che state attraversando - state parlando della normale estensione linq a Enumerazioni?

In terzo luogo, il collegamento fornito al codice ToArray e l'istruzione di blocco attorno a esso non hanno senso: senza mostrare che anche il callsite blocca la stessa raccolta, non garantisce la sicurezza del thread su al.

E così via.

+2

Avrei svalutato te, ma quell'ultima frase in realtà non era necessaria. Non tutti sono completamente al di là della teoria della sicurezza del thread e non ci si può aspettare che lo siano. –

+0

Sono corretto, come il mio testo. Ti ho revocato per avermi dato una bella lezione;) –

4

Quando la sicurezza del thread non è esplicitamente garantita dai documenti o per principio non la si può assumere. Se si assume che si rischia di mettere in produzione una classe di bug che non è attendibile e che potrebbe costare un sacco di produttività/disponibilità/denaro. Sei disposto a correre questo rischio?

Non è mai possibile testare qualcosa per essere sicuro. Non si può mai essere sicuri. Non puoi essere sicuro che una versione futura si comporta allo stesso modo.

Farlo nel modo giusto e bloccare.

Btw, queste osservazioni erano per List.ToArray che è una delle versioni più sicure di ToArray. Capisco perché uno penserebbe erroneamente che possa essere usato in concomitanza con le scritture alla lista. Ovviamente IEnumerable.ToArraynon può essere threadsafe perché è una proprietà della sequenza sottostante.

+0

Il -1 è perché la risposta è troppo verbosa e troppo generalizzata. A volte la cosa giusta da fare è bloccare, ma a volte la cosa giusta da fare è scegliere un thread o una struttura/algoritmo di dati concorrenti. Inoltre, ci * sono * test che è possibile eseguire su un codice che dovrebbe essere thread-safe nel tentativo di verificarlo. –

+0

@ 280Z28 Rispetto le tue critiche. La risposta è stata pensata per essere abbastanza generale perché il problema di fondo è lo stesso per un'intera classe di domande. Sono solo sospettoso della tua ultima affermazione: quali test eseguiresti per assicurare che ToArray sia sicuro al 100%? Nulla di meno è una spedizione di bombe in produzione. Hai mai fatto funzionare le operazioni? * Odio * ricevere 10 messaggi di errore al giorno da un bug non deterministico. – usr

+0

In realtà sono abbastanza sicuro di avere una buona conoscenza della situazione, ma la particolare presentazione potrebbe rivelarsi fuorviante per altri sviluppatori che non sono altrettanto esperti. La tua affermazione sul test non si limita a testare 'ToArray', ma dà invece l'impressione che" testing to be threadsafe "non sia possibile in generale. –

0

Sembra che si sta confondendo due cose:

  • Elenco <T> non supporta venga modificato mentre è enumerato. Quando si enumera una lista, l'enumeratore controlla se l'elenco è stato modificato dopo ogni iterazione. Calling List <T> .ToArray prima di enumerare l'elenco risolve questo problema, poiché si enumera un'istantanea dell'elenco e non l'elenco stesso.

  • Lista <T> non è una raccolta thread-safe. Tutto quanto sopra presuppone l'accesso dalla stessa discussione. L'accesso a un elenco da due thread richiede sempre un blocco. List <T> .ToArray non è thread-safe e non aiuta qui.

2

ToArray NON è protetto da thread e questo codice lo dimostra!

Considerate questo codice piuttosto ridicolo:

 List<int> l = new List<int>(); 

     for (int i = 1; i < 100; i++) 
     { 
      l.Add(i); 
      l.Add(i * 2); 
      l.Add(i * i); 
     } 

     Thread th = new Thread(new ThreadStart(() => 
     { 
      int t=0; 
      while (true) 
      { 
       //Thread.Sleep(200); 

       switch (t) 
       { 
        case 0: 
         l.Add(t); 
         t = 1; 
         break; 
        case 1: 
         l.RemoveAt(t); 
         t = 0; 
         break; 
       } 
      } 
     })); 

     th.Start(); 

     try 
     { 
      while (true) 
      { 
       Array ai = l.ToArray(); 

       //foreach (object o in ai) 
       //{ 
       // String str = o.ToString(); 
       //} 
      } 
     } 
     catch (System.Exception ex) 
     { 
      String str = ex.ToString();     
     } 

    } 

Questo codice non sarà riuscito in un tempo molto breve periodo, a causa della linea l.Add(t). Poiché lo standard ToArray non è protetto da thread, assegnerà l'array alla dimensione corrente di l, quindi aggiungeremo un elemento a l (nell'altro thread), quindi tenterà di copiare la dimensione corrente di l in ai e fallirà perché ho troppi elementi ToArray getta un ArgumentException.