2010-12-02 1040 views
10

Estoy ejecutando un bucle Parallel.For en un poco más de 7500 objetos. Dentro de ese ciclo, estoy haciendo una serie de cosas para cada uno de esos objetos, específicamente llamando a dos servicios web y dos métodos internos. Los servicios web simplemente inspeccionan el objeto, procesan y devuelven una cadena que luego configuro como una propiedad en el objeto. Lo mismo ocurre con los dos métodos internos.Paralelo.Para congelar después de alrededor de 1370 iteraciones, no tengo idea de por qué

No escribo nada en el disco ni leo desde el disco.

También actualizo la interfaz de usuario en una aplicación de winforms con una etiqueta y una barra de progreso para que el usuario sepa dónde está. Aquí está el código:

var task = Task.Factory.StartNew(() => 
{ 
    Parallel.For(0, upperLimit, (i, loopState) => 
    { 
    if (cancellationToken.IsCancellationRequested) 
     loopState.Stop(); 
    lblProgressBar.Invoke(
     (Action) 
     (() => lblProgressBar.Text = string.Format("Processing record {0} of {1}.", (progressCounter++), upperLimit))); 
    progByStep.Invoke(
     (Action) 
     (() => progByStep.Value = (progressCounter - 1))); 

     CallSvc1(entity[i]); 
     Conversion1(entity[i]); 
     CallSvc2(entity[i]); 
     Conversion2(entity[i]); 
    }); 
}, cancellationToken); 

Esto se lleva a cabo en una máquina Win7 de 32 bits.

¿Alguna idea de por qué esto se congela de repente cuando el incremento está alrededor de 1370 o más (han sido 1361, 1365 y 1371)?

¿Alguna idea sobre cómo puedo depurar esto y ver qué se bloquea si hay algo?

EDIT:
Algunas respuestas a los siguientes comentarios:
@BrokenGlass - No, no de interoperabilidad. Probaré la compilación x86 y te lo haré saber.

@chibacity - Debido a que está en una tarea de segundo plano, no congela la interfaz de usuario. Hasta el momento en que se congela, la barra de progreso y la etiqueta marcan aproximadamente 2 por segundo. Cuando se congela, simplemente deja de moverse. Puedo verificar que el número al que se detiene se haya procesado, pero no más. El uso de CPU en un núcleo dual 2.2GHz es mínimo durante la operación en 3-4% cada uno y 1-2% una vez que se congela.

@Henk Holterman - Tarda alrededor de 10-12 minutos para llegar a 1360 y sí, puedo verificar que todos esos registros se hayan procesado pero no los registros restantes.

@CodeInChaos - ¡Gracias, intentaré eso! El código funciona si elimino el paralelo, solo lleva una eternidad y un día. No he intentado restringir el número de subprocesos, pero lo haré.

EDIT 2:
Algunos detalles en cuanto a lo que está pasando con los servicios web

Básicamente lo que está pasando con los servicios web es que pasan en algunos datos y recibir datos (un XmlNode). Ese nodo se utiliza luego en el proceso Conversión1 que a su vez establece otra propiedad en la entidad que se envía al método CallSvc2, y así sucesivamente. Se ve así:

private void CallSvc1(Entity entity) 
{ 
    var svc = new MyWebService(); 
    var node = svc.CallMethod(entity.SomeProperty); 
    entity.FieldToUpdate1.LoadXml(node.InnerXml); 
} 
private void Conversion1(Entity entity) 
{ 
    // Do some xml inspection/conversion stuff 
    if (entity.FieldToUpdate1.SelectSingleNode("SomeNode") == "something") { 
     entity.FieldToUpdate2 = SomethingThatWasConverted; 
    } 
    else { 
     // Do some more logic 
    } 
} 
private void CallSvc2(Entity entity) 
{ 
    var svc = new SomeOtherWebService(); 
    var xmlNode = svc.MethodToCall(entity.FieldToUpdate2.InnerXml); 
    entity.AnotherXmlDocument.LoadXml(xmlNode.InnerXml); 
} 

Como puede ver, es bastante sencillo. Están sucediendo muchas cosas en algunos de los métodos de conversión, pero ninguno debería estar bloqueando. Y como se indica a continuación, hay 1024 subprocesos en estado de "espera" que están todos sentados en las llamadas al servicio web. Leí aquí http://www.albahari.com/threading/ que el MaxThreads está predeterminado en 1023 para .Net 4 en la máquina de 32 bits.

¿Cómo puedo liberar esos hilos de espera dado lo que tengo aquí?

+0

Tuve un problema similar antes: intentaría construir el proyecto en modo x86 para ver si eso cambia algo. ¿No harías InterOp en tus tareas? – BrokenGlass

+0

Se está congelando o simplemente va muy lento. ¿Cómo es el uso de la CPU? –

+0

Aún no trabajé con TPL, pero ¿no puede simplemente interrumpir el depurador y verificar qué método, llamar a las funciones detenidas? ¿Funciona el código si lo reemplaza con un ciclo for normal? ¿Y qué sucede si usa 'Parallel.For' pero lo restringe a uno o dos hilos? – CodesInChaos

Respuesta

9

Una posible explicación: tienes el proceso en un estado en el que no puede crear más hilos, lo que impide que el trabajo progrese, y es por eso que todo se detiene.

Francamente, independientemente de si la hipótesis es correcta o no, debe tener un enfoque completamente diferente de esto. Parallel.For es la forma incorrecta de resolver esto. (Parallel es el más adecuado para el trabajo vinculado a CPU. Lo que tiene aquí es trabajo vinculado a IO). Si realmente necesita tener miles de solicitudes de servicio web en progreso, debe pasar a usar código asíncrono, en lugar de código multiproceso . Si usa las API asincrónicas, podrá iniciar miles de solicitudes simultáneamente al usar solo un puñado de subprocesos.

Si esas solicitudes realmente se podrán ejecutar simultáneamente es otro asunto: ya sea que utilice su implementación actual de "apocalipsis de subprocesos" o una implementación asincrónica más eficiente, es posible que se encuentre en aceleración. (.NET a veces puede limitar el número de solicitudes que realmente realizará). Por lo tanto, puede solicitar hacer tantas solicitudes como desee, pero puede encontrar que casi todas sus solicitudes están esperando a que se completen las anteriores. P.ej. Creo que WebRequest limita las conexiones simultáneas a cualquier dominio individual a tan solo 2 ... ¡Activar más de 1000 hilos (o más de 1000 solicitudes de sincronización) va a generar más solicitudes de espera esperando ser una de las 2 solicitudes actuales!

Debe hacer su propia regulación. Debe decidir cuántas solicitudes pendientes debe tener simultáneamente y asegurarse de comenzar solo tantas solicitudes a la vez. Simplemente pidiendo Parallel para lanzar todas las que pueda tan rápido como pueda, todo se va a atascar.

actualizado para añadir:

Una solución rápida podría ser utilizar la sobrecarga de Parallel.For que acepta un objeto ParallelOptions - se puede establecer su propiedad MaxDegreeOfParallelism para limitar el número de solicitudes simultáneas. Eso evitaría que esta implementación de subprocesos se quede sin subprocesos. Pero sigue siendo una solución ineficiente al problema. (Y, por lo que sé, en realidad necesita hacer miles de solicitudes concurrentes. Si está escribiendo un rastreador web, por ejemplo, eso es realmente razonable de querer hacer. Parallel no es la clase correcta para ese trabajo, aunque Utilice las operaciones asincrónicas. Si los proxies del servicio web que está utilizando son compatibles con APM (BeginXxx, EndXxx), puede resumirlos en Task objects - Task.TaskFactory ofrece un FromAsync que proporcionará una tarea que representa una operación asincrónica en progreso.

Pero si va a tratar de tener miles de solicitudes en vuelo a la vez, debe pensar cuidadosamente acerca de su estrategia de aceleración. No es probable que la mejor estrategia sea lanzar solicitudes tan rápido como sea posible.

+0

Si los servicios web que intento consumir no son compatibles con APM (BeginXXX y EndXXX) pero tengo el controlador de eventos SvcNameCompleted y SvcNameAsync, ¿puedo seguir usando TaskFactory FromAsync? –

+0

No, TaskFactory.FromAsync está diseñado específicamente para APM. Sin embargo, si mira http://msdn.microsoft.com/library/dd997423, la sección "Exponer operaciones EAP complejas como tareas" muestra cómo puede tratar con el patrón XxxAsync/XxxCompleted. (El EAP, como se conoce ese patrón.) –

+0

Estoy de acuerdo en su mayoría con la parte de actualización, el número de hilos debe ser limitado. Para un número bajo, N <= 10 o más. Y eso hace que todo el patrón asíncrono sea menos importante. Si bien Ian tiene toda la razón en que APM es más eficiente, usar un puñado de hilos para esta (gran) tarea no es tan malo. –

5

Ejecute la aplicación en el depurador VS. Cuando parezca bloquearse, dígale a VS que depure: Rompe todo. Luego vaya a Depurar: Windows: hilos y observe los hilos en su proceso.Algunos de ellos deberían mostrar trazas de pila que están en su bucle for paralelo, y eso les dirá lo que estaban haciendo cuando el depurador detuvo el proceso.

+0

Hay más de 1000 subprocesos en la ventana de subprocesos de depuración y todos dicen que la ubicación del subproceso se encuentra en una de las dos llamadas al servicio web. Todos muestran amarillo y WorkerThread en la columna de categoría y dicen "In a sleep, wait, or join". ¿Podría ser este el problema, el gran volumen de hilos de espera? Si es así, ¿qué puedo hacer al respecto? No veo nada que se cuelgue o arroje excepciones. –

+0

Eso suena bastante serio. Intente retroceder un poco desde el bucle paralelo. Asegúrese de que funciona como un bucle secuencial directo, luego intente limitar la cantidad de hilos en el bucle paralelo. – dthorpe

+0

Al mirar la ventana de Tareas paralelas, se ve que son exactamente 1024 hilos de espera, todos simplemente en las llamadas de servicio web. No estoy seguro de por qué no puede liberar esos subprocesos al grupo. Los subprocesos de llamada de servicio no web se devuelven al grupo. Hmmmmmmm –

Cuestiones relacionadas