2014-12-19 9 views
6

Per esempio è il seguente thread-safe codice:Le operazioni di linq su raccolte simultanee thread safe?

ConcurrentQueue<Guid> _queue = new ConcurrentQueue<Guid>(); 
     while(true) 
     { 
      for(int y = 0; y < 3; y++) 
      { 
       if(y % 3 == 0) 
       { 
        System.Threading.Tasks.Task.Run(() => _queue.Enqueue(Guid.NewGuid())); 
       } 
       else if (y % 3 == 1) 
       { 
        Guid x; 
        System.Threading.Tasks.Task.Run(() => _queue.TryDequeue(out x)); 
       } 
       else if(y % 3 == 2) 
       { 
        System.Threading.Tasks.Task.Run(() => 
        { 
         if (_queue.Any(t => t == testGuid)) 
         { 
          // Do something 
         } 
        }); 

       } 
      } 

Edit: A quanto pare il titolo non era abbastanza chiaro in modo aggiornato il codice di esempio per includere effettivo comportamento più threaded, sì, il codice di cui sopra è solo un campione di di comportamento multithread.

+3

Dove si trova la discussione? – leppie

+0

Si può avere questo in un'app di asp.net e più thread accederà senza che li si crei manualmente – pollirrata

+1

Non c'è il multi-threading nel codice ma se ci si trova in un ambiente con multithreading ConcurrentQueue è sicuramente il tipo buono da utilizzare perché fornisce accesso sicuro alla tua coda .. – FloChanz

risposta

8

Le operazioni LINQ sono di sola lettura, quindi sono filettate su tutte le raccolte. Ovviamente, se aggiungi un codice che modifica una collezione all'interno del metodo Where o Select, cessano di essere thread-safe.

Raccolte thread-safe assicurano che le modifiche siano thread-safe, il che non è davvero un problema quando si esegue una query LINQ.

Cosa non è sicuro sta modificando una raccolta durante l'attraversamento. Le raccolte normali invalidano gli iteratori quando vengono modificati, mentre le raccolte thread-safe no. In alcuni casi, ad esempio in ConcurrentQueue, ciò viene ottenuto presentando un'istantanea dei dati durante l'iterazione.

+0

Grazie, questo è quello che stavo cercando. Quindi, se desidero che le operazioni LINQ e le modifiche alla raccolta si escludano a vicenda, devo implementare la mia raccolta di blocchi. – AncientSyntax

+0

Dipende. Può essere utile, ad esempio, racchiudere una coda in una raccolta di blocchi e esporre la dequeue come enumerabile. È quindi possibile eseguire operazioni linq su tale blocco, ma solo quando la coda è vuota. O in effetti non bloccare affatto ma fare qualcos'altro e poi tornare indietro e ripetere le operazioni di linq. Questo sarebbe contemporaneamente modificare e fare cose linq, senza bloccare e con piena sicurezza di thread. –

+0

Ciò significa che è necessario un blocco per evitare che l'istantanea contenga dati obsoleti su una query LINQ più lunga? Sei ancora vulnerabile a problemi di multithreading altrimenti. –

2

Sì, secondo documentation

Il namespace System.Collections.Concurrent fornisce diverse classi collezione thread-safe che devono essere utilizzati al posto dei tipi corrispondenti nelle System.Collections e sistema. Collections.Gli spazi dei nomi generici ogni volta che più thread sono che accede alla raccolta contemporaneamente.

+1

[Questa documentazione] (http://msdn.microsoft.com/en-us/library/dd287144%28v=vs.110%29.aspx) sarebbe più utile in quanto parla specificatamente della chiamata al metodo sulla raccolta che LINQ userà. – Rawling

3

Sì, ma ...

Prendiamo il tuo esempio:

if(_queue.Any(t => t == testGuid)) 
{ 
    // Do something 
} 

Ora questo non sarà, non importa ciò che altri thread stanno facendo, riuscire con un'eccezione se non in modi documentati (che in questo caso significa fallire con qualsiasi eccezione), inserire _queue in uno stato non valido o restituire una risposta errata.

È, come tale, thread-safe.

E adesso?

Il codice a // Do something è presumibilmente da eseguire solo se c'è un elemento nella coda che corrisponde a testGuid. Sfortunatamente non sappiamo se questo è vero o no, perché il flusso di tempo di Heraclitan è andato avanti, e tutto quello che sappiamo è che lì è stato un tale Guid di.

Ora, questo non è necessariamente inutile. Ad esempio, potremmo sapere che la coda è attualmente aggiunta solo a (forse il thread corrente è l'unico che si deseleziona ad esempio, o che tutte le operazioni di dequeue avvengono in determinate condizioni che sappiamo che non sono a posto). Allora sappiamo che c'è ancora un tale guid lì. O potremmo semplicemente voler segnalare che testGuid è stato visto se è ancora lì o no.

Ma se // Do something dipende dalla presenza di testGuid nella coda e la coda viene rimossa dalla coda, il blocco di codice nel suo complesso non è thread-safe, sebbene l'espressione di collegamento sia.