¿Hay casos en los que sea preferible utilizar un objeto Thread antiguo en lugar de una de las construcciones más nuevas?

112

Veo a mucha gente en publicaciones de blog y aquí en SO evitando o desaconsejando el uso de la Threadclase en versiones recientes de C # (y me refiero, por supuesto, a 4.0+, con la adición de Task& friends). Incluso antes, hubo debates sobre el hecho de que la funcionalidad de un hilo antiguo simple puede ser reemplazada en muchos casos por la ThreadPoolclase.

Además, otros mecanismos especializados están haciendo que la Threadclase sea menos atractiva, como Timers reemplazando el combo feo Thread+ Sleep, mientras que para las GUI tenemos BackgroundWorker, etc.

Aún así, Threadparece seguir siendo un concepto muy familiar para algunas personas (incluido yo mismo), personas que, cuando se enfrentan a una tarea que implica algún tipo de ejecución paralela, saltan directamente al uso de la vieja Threadclase. Me he estado preguntando últimamente si es hora de enmendar mis caminos.

Entonces, mi pregunta es, ¿hay algún caso en el que sea necesario o útil usar un Threadobjeto antiguo simple en lugar de una de las construcciones anteriores?

Tudor
fuente
4
No para ser quisquilloso (o tal vez ... :)), pero eso es .NET (es decir, no limitado a C #).
MetalMikester
11
Hasta ahora, el representante promedio de un usuario que responde esta pregunta es 64.5k. Una presentación bastante impresionante
zzzzBov
1
@zzzzBov: Estaba pensando en publicar mi propia respuesta, pero ahora no puedo por temor a reducir el promedio :)
Brian Gideon
@BrianGideon, no veo ninguna razón por la que eso deba impedir que usted o cualquier otra persona responda esta pregunta.
zzzzBov
@Brian Gideon: Por favor, publique. :)
Tudor

Respuestas:

107

La clase Thread no puede quedar obsoleta porque obviamente es un detalle de implementación de todos esos otros patrones que mencionas.

Pero esa no es realmente tu pregunta; tu pregunta es

¿Hay algún caso en el que sea necesario o útil utilizar un objeto Thread antiguo en lugar de una de las construcciones anteriores?

Por supuesto. Precisamente en aquellos casos en los que una de las construcciones de nivel superior no satisface sus necesidades.

Mi consejo es que si se encuentra en una situación en la que las herramientas de mayor abstracción existentes no satisfacen sus necesidades y desea implementar una solución utilizando subprocesos, entonces debe identificar la abstracción faltante que realmente necesita y luego implementar esa abstracción usando hilos , y luego use la abstracción .

Eric Lippert
fuente
33
Todo buen consejo. Pero, ¿tiene un ejemplo en el que un objeto de hilo antiguo simple podría ser preferible a una de las construcciones más nuevas?
Robert Harvey
La depuración de varios subprocesos con problemas de tiempo para cargar algún código se puede hacer mucho más fácil en ocasiones que usando otras construcciones de nivel "superior". Y de todos modos necesita los conocimientos básicos de trabajar y usar Threads.
CodingBarfield
Gracias por la respuesta, Eric. Es muy fácil olvidar, bajo el reciente bombardeo sobre las nuevas funciones de subprocesamiento, que a veces el antiguo hilo todavía es necesario. Bueno, supongo que es útil tener conocimiento del bare-metal de todos modos.
Tudor
15
@RobertHarvey: Claro. Por ejemplo, si tiene una situación clásica de "productor / consumidor", en la que desea tener un hilo dedicado a sacar cosas de una cola de trabajo y otro hilo dedicado a poner cosas en una cola de trabajo. Ambos giran para siempre; no se asignan bien a las tareas que realiza durante un tiempo y luego obtiene un resultado. No necesita un grupo de subprocesos porque solo hay dos subprocesos. Y puede ser inteligente con el uso de bloqueos y señales para asegurarse de que la cola se mantenga segura sin bloquear el otro hilo durante mucho tiempo.
Eric Lippert
@Eric: ¿No ofrece ya TPL Dataflow tal abstracción ?
Allon Guralnek
52

Los hilos son un bloque de construcción básico para ciertas cosas (a saber, paralelismo y asincronía) y, por lo tanto, no deben eliminarse. Sin embargo , para la mayoría de las personas y la mayoría de los casos de uso, hay cosas más apropiadas para usar que usted mencionó, como los grupos de subprocesos (que brindan una buena forma de manejar muchos trabajos pequeños en paralelo sin sobrecargar la máquina al generar 2000 subprocesos a la vez), BackgroundWorker( que encapsula eventos útiles para un solo trabajo de corta duración).

Pero el hecho de que en muchos casos sean más apropiados, ya que protegen al programador de reinventar innecesariamente la rueda, cometer errores estúpidos y cosas por el estilo, eso no significa que la clase Thread sea obsoleta. Todavía lo utilizan las abstracciones mencionadas anteriormente y aún lo necesitaría si necesita un control detallado sobre los subprocesos que no está cubierto por las clases más especiales.

En una línea similar, .NETno prohíbe el uso de matrices, a pesar de List<T>ser una mejor opción para muchos casos en los que la gente usa matrices. Simplemente porque es posible que aún desee crear cosas que no están cubiertas por la lib estándar.

Joey
fuente
6
El hecho de que una transmisión automática funcione el 99% del tiempo no significa que no haya valor en las transmisiones manuales, incluso ignorando las preferencias del conductor.
Guvante
4
No estoy muy familiarizado con los modismos de conducción dado que soy ciclista y usuario del transporte público. Pero una transmisión manual no es el núcleo de una automática, no es una mano mecánica que mueve una palanca. No es realmente una abstracción sobre el otro, por lo que puedo ver.
Joey
2
Puede reemplazar la palabra "hilo" por "código no administrado" en el segundo párrafo y seguirá sonando verdadero
Conrad Frix
1
@Joey: Oh, pensé que te referías a la pieza obsoleta, el valor de tener un control detallado, a costa de la usabilidad, aunque la mayoría nunca lo necesitará. Por cierto, técnicamente, la parte interesante de una transmisión manual es el embrague de todos modos.
Guvante
3
Si ustedes realmente quieren seguir la metáfora de la transmisión, la mayoría de las transmisiones secuenciales (como la transmisión SMG de BMW) son , de hecho, transmisiones manuales con un embrague y selector de marchas operados por computadora. No son sistemas de engranajes planetarios como los automáticos convencionales.
Adam Robinson
13

Tasky Threadson diferentes abstracciones. Si desea modelar un hilo, la Threadclase sigue siendo la opción más adecuada. Por ejemplo, si necesita interactuar con el hilo actual, no veo mejores tipos para esto.

Sin embargo, como señala .NET ha agregado varias abstracciones dedicadas que son preferibles Threaden muchos casos.

Brian Rasmussen
fuente
9

La Threadclase no está obsoleta, sigue siendo útil en circunstancias especiales.

Donde trabajo, escribimos un 'procesador en segundo plano' como parte de un sistema de administración de contenido: un servicio de Windows que monitorea directorios, direcciones de correo electrónico y feeds RSS, y cada vez que aparece algo nuevo, ejecuta una tarea en él, generalmente para importar el datos.

Los intentos de usar el grupo de subprocesos para esto no funcionaron: intenta ejecutar demasiadas cosas al mismo tiempo y deshacerse de los discos, así que implementamos nuestro propio sistema de sondeo y ejecución usando directamente la Threadclase.

MiMo
fuente
No tengo idea de si usar Thread directamente aquí es la solución correcta aquí, pero +1 para dar un ejemplo en el que se requería ir a Thread.
Oddthinking
6

Las nuevas opciones hacen que el uso directo y la gestión de los (costosos) hilos sean menos frecuentes.

personas que, cuando se enfrentan a una tarea que implica algún tipo de ejecución paralela, pasan directamente a utilizar la vieja clase Thread.

Lo cual es una forma muy cara y relativamente compleja de hacer cosas en paralelo.

Tenga en cuenta que el gasto es lo más importante: no puede usar un hilo completo para hacer un trabajo pequeño, sería contraproducente. ThreadPool combate los costos, la clase Task las complejidades (excepciones, espera y cancelación).

Henk Holterman
fuente
5

Para responder a la pregunta de " ¿hay algún caso en el que sea necesario o útil utilizar un objeto Thread antiguo y sencillo ", diría que un Thread antiguo sencillo es útil (pero no necesario) cuando tienes un proceso de ejecución prolongado que ganaste? nunca interactuar con desde un hilo diferente.

Por ejemplo, si está escribiendo una aplicación que se suscribe para recibir mensajes de algún tipo de cola de mensajes y su aplicación va a hacer más que solo procesar esos mensajes, entonces sería útil usar un hilo porque el hilo será autónomo (es decir, no está esperando a que se haga) y no es de corta duración. El uso de la clase ThreadPool es más para poner en cola un grupo de elementos de trabajo de corta duración y permitir que la clase ThreadPool administre el procesamiento eficiente de cada uno a medida que hay un nuevo Thread disponible. Las tareas se pueden usar donde usarías Thread directamente, pero en el escenario anterior no creo que te compren mucho. Te ayudan a interactuar con el hilo más fácilmente (lo que no ocurre con el escenario anterior

Derek Greer
fuente
Estoy de acuerdo en que la mayoría de las nuevas cosas interesantes del paralelismo están diseñadas en torno a pequeñas tareas detalladas a corto plazo, y los subprocesos de larga ejecución aún deberían implementarse con System.IO.Threading.Thread.
Ben Voigt
4

Probablemente no sea la respuesta que esperaba, pero uso Thread todo el tiempo cuando codifico contra .NET Micro Framework. MF está bastante reducido y no incluye abstracciones de nivel superior y la clase Thread es súper flexible cuando necesita obtener el último rendimiento de una CPU de bajo MHz.

AngryHacker
fuente
¡Oye, ese es realmente un buen ejemplo! Gracias. No he pensado en marcos que no admitan las nuevas funciones.
Tudor
3

Puede comparar la clase Thread con ADO.NET. No es la herramienta recomendada para hacer el trabajo, pero no es obsoleta. Otras herramientas se construyen sobre él para facilitar el trabajo.

No está mal usar la clase Thread sobre otras cosas, especialmente si esas cosas no proporcionan la funcionalidad que necesita.

Kyle Trauberman
fuente
3

Definitivamente no es obsoleto.

El problema con las aplicaciones multiproceso es que son muy difíciles de hacer bien (a menudo, el comportamiento indeterminista, la entrada, la salida y también el estado interno es importante), por lo que un programador debe impulsar tanto trabajo como sea posible al marco / herramientas. Resúmalo. Pero el enemigo mortal de la abstracción es el desempeño.

Entonces, mi pregunta es, ¿hay algún caso en el que sea necesario o útil usar un objeto Thread simple en lugar de una de las construcciones anteriores?

Iría con Threads y bloqueos solo si hubiera problemas de rendimiento graves, objetivos de alto rendimiento.

Lukasz Madon
fuente
3

Siempre he usado la clase Thread cuando necesito llevar la cuenta y controlar los hilos que he creado. Me doy cuenta de que podría usar el grupo de subprocesos para guardar todo mi trabajo sobresaliente, pero nunca he encontrado una buena manera de realizar un seguimiento de cuánto trabajo se está haciendo actualmente o cuál es el estado.

En cambio, creo una colección y coloco los hilos en ellos después de girarlos; lo último que hace un hilo es quitarse de la colección. De esa manera, siempre puedo saber cuántos subprocesos se están ejecutando y puedo usar la colección para preguntar a cada uno qué está haciendo. Si hay un caso en el que necesito eliminarlos a todos, normalmente tendrías que establecer algún tipo de indicador de "Abortar" en tu aplicación, esperar a que cada hilo se dé cuenta de eso por sí solo y terminen automáticamente; en mi caso, yo puede recorrer la colección y emitir un Thread.Abort a cada uno por turno.

En ese caso, no he encontrado una mejor manera que trabajar directamente con la clase Thread. Como mencionó Eric Lippert, los otros son solo abstracciones de nivel superior, y es apropiado trabajar con las clases de nivel inferior cuando las implementaciones de alto nivel disponibles no satisfacen sus necesidades. Así como a veces necesita hacer llamadas a la API de Win32 cuando .NET no cubre sus necesidades exactas, siempre habrá casos en los que la clase Thread sea la mejor opción a pesar de los "avances" recientes.

SqlRyan
fuente