2011-11-29 10 views
8

Sto usando .AsParallel(). ForAll() per enumerare una raccolta in parallelo nel contesto di una richiesta ASP.NET. Il metodo di enumerazione si basa su System.Threading.Thread.CurrentPrincipal.L'identità del thread viene trasferita quando si utilizzano le estensioni PLINQ?

Posso fare affidamento sui singoli thread utilizzati per avere System.Threading.Thread.CurrentPrincipal impostato su HttpContext.Current.User del thread che sta elaborando la richiesta ASP.NET o devo gestirlo personalmente?

Un altro modo di porre la domanda è che i thread utilizzati da PLINQ ereditano l'identità del thread che ha invocato l'operazione?

+0

Vedere se il campo supporto è '[ThreadStatic]', se non, allora si dovrebbe essere ok. – leppie

+0

@joeenzminger Vedi qui http://stackoverflow.com/a/13049286/50776 per un test che mostra che PLINQ scorre 'ExecutionContext' (e quindi' Thread.CurrentPrincipal'). – casperOne

risposta

11

No, l'identità non verrà propagata automaticamente a questi thread di lavoro. Se, infatti, i componenti che stai utilizzando sono HttpContext.User, ciò che puoi fare è catturare l'istanza "ambiente" corrente "HttpContext" nel tuo thread "principale" e propagarla ai tuoi thread di lavoro. Che qualcosa sarebbe simile a questa:

HttpContext currentHttpContext = HttpContext.Current; 

myWorkItems.AsParallel().ForAll(wi => 
{ 
    HttpContext.Current = currentHttpContext; 

    try 
    { 
     // anything called from here out will find/use the context of your original ASP.NET thread 
    } 
    finally 
    { 
     // Disassociate the context from the worker thread so that it is not held on to beyond its official lifetime 
     HttpContext.Current = null; 
    } 
}); 

Questo funziona perché HttpContext.Current è sostenuta da un filo statico, così ogni thread di lavoro verrà assegnato l'istanza dal thread principale e qualsiasi lavoro svolto su di esso da quel punto vedrà che come l'istanza corrente.

Ora, è necessario essere consapevoli del fatto che HttpContext e le relative classi non sono state progettate per essere thread-safe, quindi questo è un po 'un hack. Se stai leggendo solo da proprietà questo non è davvero un problema. Se non si utilizzano componenti che si basano su HttpContext.Current, sarebbe "più pulito" non impostarlo e utilizzare semplicemente la variabile acquisita currentHttpContext direttamente nell'operatore.

Infine, se tutto ciò che realmente serve è quello di propagare la principale corrente per i thread di lavoro, allora si può fare proprio questo, invece utilizzando lo stesso approccio:

Principal logicalPrincipal = Thread.CurrentPrincipal; 

myWorkItems.AsParallel().ForAll(wi => 
{ 
    Principal originalWorkerThreadPrincipal = Thread.CurrentPrincipal; 
    Thread.CurrentPrincipal = logicalPrincipal; 

    try 
    { 
     // anything called from here out will find the principal from your original thread 
    } 
    finally 
    { 
     // Revert to the original identity when work is complete 
     Thread.CurrentPrincipal = originalWorkerThreadPrincipal; 
    } 
}); 
+0

Grazie per la risposta. Questo è quello che pensavo, ma ho pensato che ci sarebbe stata una possibilità che almeno il tuo secondo esempio stesse accadendo sotto il cofano nell'impianto idraulico PLINQ. –

+0

Nota che 'Thread.CurrentPrincipal' * fa * il flusso, vedi http://stackoverflow.com/a/13049286/50776 che mostra come. – casperOne

3

Questa è l'implementazione dietro CurrentPrincipal

public static IPrincipal CurrentPrincipal 
{ 
    get 
    { 
     lock (CurrentThread) 
     { 
      IPrincipal threadPrincipal = CallContext.Principal; 
      if (threadPrincipal == null) 
      { 
       threadPrincipal = GetDomain().GetThreadPrincipal(); 
       CallContext.Principal = threadPrincipal; 
      } 
      return threadPrincipal; 
     } 
    } 
    set { CallContext.Principal = value; } 
} 

Tutte le discussioni di nuova creazione avranno nulla e sarà presa dal dominio di applicazione. Quindi dovrebbe essere ok. Tuttavia devi essere attento con la cultura. Non sarà derivato dall'avvio del thread. Vedi: Parallel Programing, PLINQ and Globalization

+1

Hmm. Un piccolo bersaglio. Penso che i thread utilizzati da PLINQ provengano dal pool di thread. La mia domanda è: PLINQ si preoccupa di inizializzare CurrentPrincipal dal thread che avvia la chiamata parallela? Lo imposta su null quando è finito? La tua risposta ha più a che fare con ciò che accade, in generale, quando un thread viene creato da zero o se CurrentPrincipal è stato impostato su null. –

1

Una cosa sottile da notare quando passa Principal attraverso .AsParallel() boundary: dove si materializza la sequenza?

Questo non è un problema con .ForAll(), ma prendere in considerazione un altro scenario:

var result = items.AsParallel().Select(MyTransform); 

allora si sta passando risultato altrove in modo che attraversa confine filo (che è probabile, diciamo, se lo stai restituendo dal metodo di azione WCF).

In questo caso entro il tempo MyTransform viene applicato, Thread. Il valore CurrentPrincipal potrebbe contenere qualcosa di inaspettato.

Quindi, la soluzione è qui per materializzare la query sul posto (chiamando .ToArray(), ToList(), etc.)

Problemi correlati