2011-11-29 12 views
8

Estoy usando .AsParallel(). ForAll() para enumerar una colección en paralelo en el contexto de una solicitud de ASP.NET. El método de enumeración se basa en System.Threading.Thread.CurrentPrincipal.¿Se transfiere la identidad del subproceso al usar extensiones PLINQ?

¿Puedo confiar en los subprocesos individuales utilizados para tener su System.Threading.Thread.CurrentPrincipal configurado en HttpContext.Current.User del subproceso que está procesando la solicitud de ASP.NET o tengo que administrarlo por mí mismo?

Otra forma de hacer la pregunta es: ¿los hilos utilizados por PLINQ heredan la identidad del hilo que invocó la operación?

+0

Vea si el campo de respaldo es '[ThreadStatic]', de lo contrario, debería estar bien. – leppie

+0

@joeenzminger Vea aquí http://stackoverflow.com/a/13049286/50776 para una prueba que muestra que PLINQ fluye 'ExecutionContext' (y por lo tanto' Thread.CurrentPrincipal'). – casperOne

Respuesta

11

No, la identidad no se propagará automáticamente a estos subprocesos de trabajo. Si, de hecho, los componentes que está usando son HttpContext.User, lo que puede hacer es capturar la instancia actual, "ambiente" HttpContext en su subproceso "principal" y propagarla a sus subprocesos de trabajo. Eso sería algo como esto:

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; 
    } 
}); 

Esto funciona porque HttpContext.Current está respaldado por un hilo estática, por lo que cada subproceso de trabajo se le asignará la instancia de su hilo principal y cualquier trabajo realizado sobre él desde ese punto veremos que como la instancia actual.

Ahora, debe tener en cuenta que HttpContext y sus clases relacionadas no se diseñaron para ser seguras para subprocesos, por lo que se trata de un truco. Si solo lees de las propiedades, esto no es realmente un problema. Si no está usando componentes que se basan en HttpContext.Current, entonces sería "más limpio" no configurar eso y en su lugar solo use la variable currentHttpContext capturada directamente en el trabajador.

Por último, si todo lo que realmente necesita es propagar el director actual de los subprocesos de trabajo a continuación, puede hacer precisamente eso en lugar de utilizar el mismo enfoque:

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

Gracias por la respuesta. Eso es lo que pensé, pero pensé que podría haber una posibilidad de que al menos tu segundo ejemplo estuviera sucediendo bajo el capó de las tuberías de PLINQ. –

+0

Tenga en cuenta que 'Thread.CurrentPrincipal' * does * flow, consulte http://stackoverflow.com/a/13049286/50776 que muestra cómo. – casperOne

3

Esta es la implementación detrás 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; } 
} 

Todas las roscas de nueva creación tendrán nulo y que serán tomados de dominio de aplicación. Entonces debería estar bien. Sin embargo, debes tener cuidado con la cultura. No se derivará del hilo de inicio. Ver: Parallel Programing, PLINQ and Globalization

+1

Hmm. Un poco fuera de objetivo. Creo que los hilos que utiliza PLINQ provienen del grupo de subprocesos. Mi pregunta es si PLINQ se encarga de inicializar CurrentPrincipal del hilo que inicia la llamada paralela. ¿Lo establece en nulo cuando está terminado? Su respuesta tiene más que ver con lo que sucede, en general, cuando se crea un hilo desde cero o si CurrentPrincipal se ha establecido como nulo. –

1

Una cosa sutil a notar al pasar principal a través de .AsParallel() límite: ¿Dónde se materializa su secuencia?

Esto no es un problema con .ForAll(), pero hay que considerar otro escenario:

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

Entonces estás pasando resultado otro lugar, así que cruza límites hilo (lo cual es probable, digamos, si lo está devolviendo del método de acción WCF).

En este caso por el momento se aplica MyTransform, Subproceso. El valor de CurrentPrincipal puede contener algo inesperado.

Por lo tanto, la solución aquí es materializar la consulta sobre el terreno (llamando al .ToArray(), .ToList(), etc.)

Cuestiones relacionadas