Tiendo a responder muchas preguntas relacionadas con el multihilo y a menudo veo la misma pregunta básica de diferentes maneras. Presentaré los problemas más comunes como los he visto a lo largo de los años y explicaré cómo las tecnologías más nuevas han hecho que la solución de estos problemas sea más fácil.
cerrándose sobre la variable de bucle
Esto no es un problema específico de rosca, pero el uso de enhebrar definitivamente magnifica el problema. C# 5.0 corrige este problema para el bucle foreach
creando una nueva variable para cada iteración. Ya no tendrá que crear una variable especial para cierres de expresiones lambda. Desafortunadamente, el bucle for
todavía necesitará ser manejado con una variable de captura especial.
la espera de tareas asíncronas para completar
.NET 4.0 introdujo la clase CountdownEvent
que encapsula una gran cantidad de la lógica necesaria para esperar a la finalización de las muchas tareas. La mayoría de los desarrolladores junior usa Thread.Join
llamadas o una sola llamada WaitHandle.WaitAll
. Ambos tienen problemas de escalabilidad. El viejo patrón era usar un solo ManualResetEvent
y señalarlo cuando un contador llegaba a cero. El contador se actualizó utilizando la clase Interlocked
. CountdownEvent
hace que este patrón sea mucho más fácil. Solo recuerde tratar a su principal como trabajador para evitar esa sutil condición de carrera que puede ocurrir si un trabajador termina antes de que todos los trabajadores hayan sido puestos en cola.
.NET 4.0 también introdujo la clase Task
que puede tener tareas hijo encadenadas a través de TaskCreationOptions.AttachedToParent
. Si llama al Task.Wait
en un padre esperará a que se completen todas las tareas secundarias.
productor-consumidor
.NET 4.0 introdujo la clase BlockingCollection
que actúa como una cola normal, excepto que puede bloquear cuando la colección está vacía. Puede poner en cola un objeto llamando al Add
y quitar la cola de un objeto llamando al Take
. Take
bloques hasta que haya un artículo disponible. Esto simplifica considerablemente la lógica del productor-consumidor. Solía ocurrir que los desarrolladores intentaban escribir su propia clase de cola de bloqueo. Pero, si no sabes lo que estás haciendo, entonces realmente puedes arruinarlo ... mal. De hecho, durante mucho tiempo, Microsoft tenía un ejemplo de cola de bloqueo en la documentación de MSDN que estaba mal roto. Afortunadamente, desde entonces se eliminó.
Actualización de la interfaz de usuario con el progreso subproceso de trabajo
La introducción de BackgroundWorker
hizo la escisión de una tarea de fondo desde una aplicación WinForm mucho más fácil para los desarrolladores novatos. La principal ventaja es que puede llamar al ReportProgress
desde el controlador de eventos DoWork
y los controladores de eventos ProgressChanged
se ordenarán automáticamente en el hilo de la interfaz de usuario. Por supuesto, cualquiera que rastree mis respuestas en SO sabe cómo me siento acerca de las operaciones de clasificación (a través del Invoke
o similar) como una solución para actualizar la UI con información de progreso simple. Lo rasgo todo el tiempo porque generalmente es un enfoque terrible. BackgroundWorker
todavía obliga al desarrollador a un modelo de inserción (mediante operaciones de clasificación en segundo plano), pero al menos hace todo esto detrás de escena.
La falta de elegancia de Invoke
Todos sabemos que un elemento de interfaz de usuario sólo se puede acceder desde el subproceso de interfaz de usuario. Esto generalmente significaba que un desarrollador tenía que usar las operaciones de cálculo de referencias a través de ISynchronizeInvoke
, DispatcherObject
o SynchronizationContext
para transferir el control nuevamente al hilo de la interfaz de usuario. Pero admitámoslo. Estas operaciones de clasificación se ven feas. Task.ContinueWith
hizo esto un poco más elegante, pero la verdadera gloria es await
como parte del nuevo modelo de programación asincrónica de C# 5. await
se puede utilizar para esperar a que Task
se complete de tal manera que el control de flujo se interrumpa temporalmente mientras se ejecuta la tarea y luego se devuelve en ese mismo lugar en el contexto de sincronización correcto. No hay nada más elegante y satisfactorio que usar await
como reemplazo de todas esas llamadas Invoke
.
Programación paralela
A menudo veo preguntas preguntando cómo las cosas pueden ocurrir en paralelo. La forma antigua era crear algunos hilos o usar el ThreadPool
. .NET 4.0 dio uso de TPL y PLINQ. La clase Parallel
es una excelente forma de obtener las iteraciones de un ciclo en paralelo. Y el AsParallel
de PLINQ es un lado diferente de la misma moneda para el LINQ antiguo. Estas nuevas características de TPL simplifican enormemente esta categoría de programación multiproceso.
.NET 4.5 presenta la biblioteca TPL Data Flow. Está destinado a hacer elegante un complejo problema de programación en paralelo. Resume clases en bloques. Pueden ser bloques de destino o bloques de origen. Los datos pueden fluir de un bloque a otro. Hay muchos bloques diferentes, incluidos BufferBlock<T>
, BroadcastBlock<T>
, ActionBlock<T>
, etc. que hacen cosas diferentes. Y, por supuesto, toda la biblioteca estará optimizada para su uso con las nuevas palabras clave async
y await
. Es un nuevo conjunto de clases emocionantes que creo que lentamente se pondrán de moda.
finalización de precaución
¿Cómo se obtiene un hilo que pare? Veo esta pregunta mucho. La forma más fácil es llamar al Thread.Abort
, pero todos sabemos los peligros de hacer esto ... espero. Hay muchas formas diferentes de hacerlo de forma segura. .NET 4.0 introdujo un concepto más unificado llamado cancelación a través de CancellationToken
y CancellationTokenSource
. Las tareas en segundo plano pueden sondear IsCancellationRequested
o simplemente llamar al ThrowIfCancellationRequested
en puntos seguros para interrumpir con gracia cualquier trabajo que estén haciendo. Otros hilos pueden llamar al Cancel
para solicitar la cancelación.
Los delegados y eventos no tienen nada que ver con el subprocesamiento, y no son específicos de .NET 4.5. Sé más claro, ya que no puedo decir de qué estás hablando. –
@JohnSaunders Los delegados Async facilitan el enhebrado; vea http://msdn.microsoft.com/en-us/library/22t547yb.aspx – LamonteCristo
¿Ha notado que el uso asincrónico de delegados ha estado en .NET desde la versión 1.0? –