Siempre tuve la impresión de que el uso de ThreadPool para tareas en segundo plano de corta duración (digamos no críticas) se consideraba la mejor práctica, incluso en ASP.NET, pero luego encontré este artículo que parece sugerir lo contrario: el El argumento es que debe dejar ThreadPool para ocuparse de las solicitudes relacionadas con ASP.NET.
Así que así es como he estado haciendo pequeñas tareas asincrónicas hasta ahora:
ThreadPool.QueueUserWorkItem(s => PostLog(logEvent))
Y el artículo sugiere, en cambio, crear un hilo explícitamente, similar a:
new Thread(() => PostLog(logEvent)){ IsBackground = true }.Start()
El primer método tiene la ventaja de ser administrado y limitado, pero existe la posibilidad (si el artículo es correcto) de que las tareas en segundo plano compitan por subprocesos con los controladores de solicitudes ASP.NET. El segundo método libera ThreadPool, pero a costa de no estar limitado y, por lo tanto, consumir potencialmente demasiados recursos.
Entonces, mi pregunta es, ¿es correcto el consejo del artículo?
Si su sitio estaba recibiendo tanto tráfico que su ThreadPool se estaba llenando, entonces es mejor salir de banda, o un ThreadPool completo implicaría que está llegando al límite de sus recursos de todos modos, en cuyo caso usted ¿No deberías intentar iniciar tus propios hilos?
Aclaración: solo pregunto en el ámbito de pequeñas tareas asincrónicas no críticas (por ejemplo, registro remoto), no elementos de trabajo costosos que requerirían un proceso separado (en estos casos, estoy de acuerdo en que necesitará una solución más sólida).
fuente
Respuestas:
Otras respuestas aquí parecen estar omitiendo el punto más importante:
A menos que esté tratando de paralelizar una operación con uso intensivo de CPU para hacerlo más rápido en un sitio de baja carga, no tiene sentido usar un hilo de trabajo en absoluto.
Eso se aplica tanto a los hilos libres, creados por
new Thread(...)
, como a los hilos de trabajo en elThreadPool
que responden a lasQueueUserWorkItem
solicitudes.Sí, es cierto, puede matar de hambre
ThreadPool
en un proceso ASP.NET poniendo en cola demasiados elementos de trabajo. Evitará que ASP.NET procese más solicitudes. La información del artículo es precisa a ese respecto; el mismo grupo de subprocesos utilizadoQueueUserWorkItem
también se utiliza para atender solicitudes.Pero si realmente está haciendo cola suficientes elementos de trabajo para causar esta inanición, ¡entonces debería estar privando al grupo de subprocesos! Si está ejecutando literalmente cientos de operaciones con uso intensivo de la CPU al mismo tiempo, ¿de qué le serviría tener otro hilo de trabajo para atender una solicitud ASP.NET, cuando la máquina ya está sobrecargada? Si se encuentra en esta situación, ¡debe rediseñarlo por completo!
La mayoría de las veces que veo o escucho que el código multiproceso se usa de manera inapropiada en ASP.NET, no es para poner en cola el trabajo intensivo de la CPU. Es para poner en cola trabajos vinculados a E / S. Y si desea realizar un trabajo de E / S, debería utilizar un hilo de E / S (puerto de finalización de E / S).
Específicamente, debería usar las devoluciones de llamada asíncronas compatibles con cualquier clase de biblioteca que esté usando. Estos métodos siempre están etiquetados con mucha claridad; comienzan con las palabras
Begin
yEnd
. Al igual que enStream.BeginRead
,Socket.BeginConnect
,WebRequest.BeginGetResponse
, y así sucesivamente.Estos métodos hacen uso de la
ThreadPool
, pero utilizan IOCPs, que no no interfieren con las solicitudes de ASP.NET. Son un tipo especial de hilo ligero que se puede "despertar" mediante una señal de interrupción del sistema de E / S. Y en una aplicación ASP.NET, normalmente tiene un subproceso de E / S para cada subproceso de trabajo, por lo que cada solicitud puede tener una operación asíncrona en cola. Eso es literalmente cientos de operaciones asíncronas sin ninguna degradación significativa del rendimiento (asumiendo que el subsistema de E / S puede mantenerse al día). Es mucho más de lo que necesitará.Solo tenga en cuenta que los delegados asíncronos no funcionan de esta manera; terminarán usando un hilo de trabajo, como
ThreadPool.QueueUserWorkItem
. Solo los métodos asíncronos integrados de las clases de biblioteca de .NET Framework son capaces de hacer esto. Puede hacerlo usted mismo, pero es complicado y un poco peligroso y probablemente más allá del alcance de esta discusión.La mejor respuesta a esta pregunta, en mi opinión, es no usar
ThreadPool
o unaThread
instancia en segundo plano en ASP.NET . No es en absoluto como hacer girar un hilo en una aplicación de Windows Forms, donde lo hace para mantener la interfaz de usuario receptiva y no le importa cuán eficiente sea. En ASP.NET, su preocupación es el rendimiento , y todo ese cambio de contexto en todos esos subprocesos de trabajo va a matar absolutamente su rendimiento, ya sea que useThreadPool
o no.Por favor, si se encuentra escribiendo código de subprocesamiento en ASP.NET, considere si se puede reescribir o no para usar métodos asincrónicos preexistentes, y si no puede, considere si realmente necesita el código o no para ejecutarse en un hilo de fondo. En la mayoría de los casos, probablemente agregará complejidad sin ningún beneficio neto.
fuente
Action<T>
como devolución de llamada, por ejemplo. Si quiere decir que la elección de utilizar un hilo de trabajo o un hilo de E / S ocurre en el nivel más bajo, eso es intencional; solo ese nivel puede decidir si necesita o no un IOCP.ThreadPool
que te limita de esta manera, probablemente porque no confiaban en que los desarrolladores lo hicieran bien. El grupo de subprocesos de Windows no administrado tiene una API muy similar, pero en realidad le permite elegir el tipo de subproceso.Según Thomas Marquadt, del equipo ASP.NET de Microsoft, es seguro utilizar ASP.NET ThreadPool (QueueUserWorkItem).
Del artículo :
fuente
Los sitios web no deberían dar vueltas en torno a los hilos de generación.
Por lo general, mueve esta funcionalidad a un servicio de Windows con el que luego se comunica (yo uso MSMQ para hablar con ellos).
- Editar
Describí una implementación aquí: Procesamiento en segundo plano basado en cola en la aplicación web ASP.NET MVC
- Editar
Para expandir por qué esto es incluso mejor que solo hilos:
Con MSMQ, puede comunicarse con otro servidor. Puede escribir en una cola en todas las máquinas, por lo que si determina, por alguna razón, que su tarea en segundo plano está consumiendo demasiado los recursos del servidor principal, puede cambiarla de manera bastante trivial.
También le permite procesar por lotes cualquier tarea que estuviera intentando hacer (enviar correos electrónicos / lo que sea).
fuente
Definitivamente creo que la práctica general para el trabajo asincrónico rápido y de baja prioridad en ASP.NET sería usar el grupo de subprocesos .NET, particularmente para escenarios de alto tráfico, ya que desea que sus recursos estén limitados.
Además, la implementación de subprocesos está oculta: si comienza a generar sus propios subprocesos, también debe administrarlos correctamente. No digo que no puedas hacerlo, pero ¿por qué reinventar esa rueda?
Si el rendimiento se convierte en un problema y puede establecer que el grupo de subprocesos es el factor limitante (y no las conexiones de la base de datos, las conexiones de red salientes, la memoria, los tiempos de espera de la página, etc.), entonces modifica la configuración del grupo de subprocesos para permitir más subprocesos de trabajo, solicitudes en cola más altas etc.
Si no tiene un problema de rendimiento, elegir generar nuevos subprocesos para reducir la contención con la cola de solicitudes ASP.NET es una optimización prematura clásica.
Sin embargo, idealmente no necesitaría usar un subproceso separado para realizar una operación de registro; simplemente habilite el subproceso original para completar la operación lo más rápido posible, que es donde MSMQ y un subproceso / proceso de consumidor separado entran en escena. Estoy de acuerdo en que esto es más pesado y requiere más trabajo de implementar, pero realmente necesita la durabilidad aquí: la volatilidad de una cola compartida en memoria rápidamente desgastará su bienvenida.
fuente
Debe usar QueueUserWorkItem y evitar la creación de nuevos hilos como evitaría la plaga. Para una imagen que explique por qué no dejará pasar hambre a ASP.NET, ya que usa el mismo ThreadPool, imagine a un malabarista muy hábil que usa las dos manos para mantener media docena de bolos, espadas o lo que sea en vuelo. Para tener una idea visual de por qué crear sus propios hilos es malo, imagine lo que sucede en Seattle en la hora pico cuando las rampas de entrada a la autopista muy utilizadas permiten que los vehículos ingresen al tráfico de inmediato en lugar de usar una luz y limitar el número de entradas a una cada pocos segundos. . Finalmente, para obtener una explicación detallada, consulte este enlace:
http://blogs.msdn.com/tmarq/archive/2010/04/14/performing-asynchronous-work-or-tasks-in-asp-net-applications.aspx
Gracias Thomas
fuente
Ese artículo no es correcto. ASP.NET tiene su propio grupo de subprocesos, subprocesos de trabajo administrados, para atender las solicitudes de ASP.NET. Este grupo suele tener unos pocos cientos de subprocesos y está separado del grupo ThreadPool, que es un múltiplo más pequeño de procesadores.
El uso de ThreadPool en ASP.NET no interferirá con los subprocesos de trabajo de ASP.NET. Usar ThreadPool está bien.
También sería aceptable configurar un solo hilo que sea solo para registrar mensajes y usar el patrón productor / consumidor para pasar mensajes de registro a ese hilo. En ese caso, dado que el hilo es de larga duración, debe crear un único hilo nuevo para ejecutar el registro.
Definitivamente, usar un hilo nuevo para cada mensaje es excesivo.
Otra alternativa, si solo está hablando de registro, es usar una biblioteca como log4net. Maneja el registro en un hilo separado y se ocupa de todos los problemas de contexto que podrían surgir en ese escenario.
fuente
Yo diría que el artículo está mal. Si está ejecutando una gran tienda .NET, puede usar el grupo de manera segura en múltiples aplicaciones y múltiples sitios web (usando grupos de aplicaciones separados), simplemente según una declaración en la documentación de ThreadPool :
fuente
System.Threading.ThreadPool
.Me hicieron una pregunta similar en el trabajo la semana pasada y les daré la misma respuesta. ¿Por qué utiliza aplicaciones web de subprocesos múltiples por solicitud? Un servidor web es un sistema fantástico optimizado en gran medida para proporcionar muchas solicitudes de manera oportuna (es decir, múltiples subprocesos). Piense en lo que sucede cuando solicita casi cualquier página de la web.
Usted da el ejemplo del registro remoto, pero eso debería ser una preocupación para su registrador. Debe existir un proceso asincrónico para recibir mensajes de manera oportuna. Sam incluso señala que su registrador (log4net) ya debería admitir esto.
Sam también tiene razón en que el uso del grupo de subprocesos en CLR no causará problemas con el grupo de subprocesos en IIS. Sin embargo, lo que debe preocuparse aquí es que no está generando subprocesos de un proceso, está generando nuevos subprocesos a partir de subprocesos de IIS. Hay una diferencia y la distinción es importante.
Fuente
fuente
No estoy de acuerdo con el artículo de referencia (C # feeds.com). Es fácil crear un hilo nuevo pero peligroso. El número óptimo de subprocesos activos para ejecutar en un solo núcleo es sorprendentemente bajo: menos de 10. Es demasiado fácil hacer que la máquina pierda tiempo cambiando subprocesos si los subprocesos se crean para tareas menores. Los hilos son un recurso que REQUIERE gestión. La abstracción WorkItem está ahí para manejar esto.
Aquí hay una compensación entre reducir la cantidad de subprocesos disponibles para las solicitudes y crear demasiados subprocesos para permitir que cualquiera de ellos se procese de manera eficiente. Esta es una situación muy dinámica, pero creo que debería ser administrada activamente (en este caso por el grupo de subprocesos) en lugar de dejar que el procesador se mantenga por delante de la creación de subprocesos.
Finalmente, el artículo hace algunas declaraciones bastante amplias sobre los peligros de usar ThreadPool, pero realmente necesita algo concreto que las respalde.
fuente
Si IIS usa o no el mismo ThreadPool para manejar las solicitudes entrantes, parece difícil obtener una respuesta definitiva, y también parece haber cambiado en las versiones. Por lo tanto, parecería una buena idea no usar excesivamente los subprocesos ThreadPool, para que IIS tenga muchos de ellos disponibles. Por otro lado, generar tu propio hilo para cada pequeña tarea parece una mala idea. Presumiblemente, tiene algún tipo de bloqueo en su registro, por lo que solo un hilo podría progresar a la vez, y el resto solo se turnaría para programar y no programar (sin mencionar la sobrecarga de generar un nuevo hilo). Esencialmente, te encuentras con los problemas exactos para los que se diseñó ThreadPool.
Parece que un compromiso razonable sería que su aplicación asigne un único hilo de registro al que pueda pasar mensajes. Debería tener cuidado de que el envío de mensajes sea lo más rápido posible para no ralentizar su aplicación.
fuente
Puede usar Parallel.For o Parallel.ForEach y definir el límite de posibles subprocesos que desea asignar para que se ejecuten sin problemas y evitar la falta de grupos.
Sin embargo, al ejecutarlo en segundo plano, deberá utilizar el estilo TPL puro a continuación en la aplicación web ASP.Net.
fuente