c# - thread - Diferencias entre tareas y subprocesos
tareas en c# (4)
La clase Thread
se utiliza para crear y manipular un thread en Windows.
Una Task
representa una operación asíncrona y forma parte de la biblioteca paralela de tareas , un conjunto de API para ejecutar tareas de forma asíncrona y en paralelo.
En los días de antaño (es decir, antes de TPL) solía ser que el uso de la clase Thread
era una de las formas estándar de ejecutar el código en segundo plano o en paralelo (una mejor alternativa era usar un ThreadPool
), sin embargo, esto era engorroso. y tenía varias desventajas, entre las cuales se encontraba la sobrecarga de rendimiento de la creación de un nuevo subproceso para realizar una tarea en segundo plano.
Hoy en día, el uso de tareas y el TPL es una solución mucho mejor el 90% del tiempo, ya que proporciona abstracciones que permiten un uso mucho más eficiente de los recursos del sistema. Imagino que hay algunos escenarios en los que desea un control explícito sobre el hilo en el que está ejecutando su código, sin embargo, en general, si desea ejecutar algo de forma asincrónica, su primer puerto de llamada debe ser el TPL.
Esta pregunta ya tiene una respuesta aquí:
- ¿Cuál es la diferencia entre tarea y hilo? 8 respuestas
Soy nuevo en la programación paralela. Hay dos clases disponibles en .NET: Task
y Thread
.
Entonces, la pregunta es: ¿Cuál es la diferencia entre esas clases? ¿Cuándo es mejor usar Thread
y cuándo Task
?
Por lo general, oirás que Task es un concepto de nivel más alto que el hilo ... y eso es lo que significa esta frase:
No puede usar Abort / ThreadAbortedException, debe admitir el evento de cancelación en su "código de negocio" periódicamente probando el
token.IsCancellationRequested
flag (también evite conexiones largas o sin tiempo de espera, por ejemplo, a db, de lo contrario nunca tendrá la oportunidad de probar este flag). Por el mismo motivo,Thread.Sleep(delay)
debe reemplazarse conTask.Delay(delay, token);
No hay funcionalidad de los métodos de
Suspend
ySuspend
de subprocesos con tareas. La instancia de tarea tampoco puede ser reutilizada .Pero obtienes dos nuevas herramientas:
a) continuaciones
// continuation with ContinueWhenAll - execute the delegate, when ALL // tasks[] had been finished; other option is ContinueWhenAny Task.Factory.ContinueWhenAll( tasks, () => { int answer = tasks[0].Result + tasks[1].Result; Console.WriteLine("The answer is {0}", answer); } );
b) tareas anidadas / hijo
//StartNew - starts task immediately, parent ends whith child var parent = Task.Factory.StartNew (() => { var child = Task.Factory.StartNew(() => { //... }); }, TaskCreationOptions.AttachedToParent );
Así que el hilo del sistema está completamente oculto de la tarea, pero el código de la tarea se ejecuta en el hilo del sistema concreto. Los subprocesos del sistema son recursos para tareas y, por supuesto, todavía hay un conjunto de subprocesos bajo el capó de la ejecución paralela de la tarea. Puede haber diferentes estrategias de cómo subprocesos conseguir nuevas tareas para ejecutar. Otro recurso compartido TaskScheduler se preocupa por ello. Algunos problemas que TaskScheduler resuelve 1) prefieren ejecutar la tarea y su configuración en el mismo hilo minimizando el costo de conmutación (también conocido como ejecución en línea ) 2) prefieren ejecutar las tareas en el orden en que se iniciaron - también conocido como PreferFairness 3) distribución más efectiva de tareas entre hilos inactivos Dependiendo del "conocimiento previo de la actividad de tareas", también conocido como Robo de trabajo . Importante: en general "async" no es lo mismo que "paralelo". Al jugar con las opciones de TaskScheduler, puede configurar tareas asíncronas que se ejecutarán en un subproceso de forma síncrona. Para expresar la ejecución paralela del código, se pueden utilizar abstracciones más altas (que las Tareas):
Parallel.ForEach
,PLINQ
,Dataflow
.Las tareas están integradas con funciones C # async /
requestButton.Clicked += async (o, e) => ProcessResponce(await client.RequestAsync(e.ResourceName));
tambiénrequestButton.Clicked += async (o, e) => ProcessResponce(await client.RequestAsync(e.ResourceName));
como Promise Model , por ejemplo,requestButton.Clicked += async (o, e) => ProcessResponce(await client.RequestAsync(e.ResourceName));
la ejecución declient.RequestAsync
no bloqueará el subproceso de la interfaz de usuario. Importante: bajo el capó La llamada del delegado que se hizoClicked
es absolutamente regular (todo el subproceso lo realiza el compilador).
Eso es suficiente para hacer una elección. Si necesita admitir la función Cancelar de la API heredada de llamada que tiende a colgarse (por ejemplo, la conexión sin tiempo) y, en este caso, es compatible con Thread.Abort (), o si está creando cálculos de fondo de subprocesos múltiples y desea optimizar el cambio entre subprocesos utilizando Suspender / Reanudar , eso significa administrar manualmente la ejecución paralela - quedarse con Thread. De lo contrario, vaya a Tareas porque le permitirán manipularlos fácilmente en grupos de ellos, se integrarán en el lenguaje y harán que los desarrolladores sean más productivos en la Biblioteca paralela de tareas (TPL) .
Hilo
El subproceso representa un subproceso real a nivel del sistema operativo, con sus propios recursos de pila y kernel. (técnicamente, una implementación de CLR podría usar fibras en su lugar, pero ningún CLR existente hace esto) El hilo permite el mayor grado de control; puede Abortar () o Suspender () o Reanudar () un subproceso (aunque esto es una idea muy mala), puede observar su estado y puede establecer propiedades de nivel de subproceso como el tamaño de la pila, el estado del apartamento o la cultura.
El problema con Thread es que los hilos de sistema operativo son costosos. Cada subproceso que consume consume una cantidad no trivial de memoria para su pila, y agrega una sobrecarga de CPU adicional como el cambio de contexto del procesador entre subprocesos. En su lugar, es mejor tener un pequeño grupo de subprocesos que ejecuten su código a medida que el trabajo esté disponible.
Hay momentos en que no hay un hilo alternativo. Si necesita especificar el nombre (para fines de depuración) o el estado del apartamento (para mostrar una UI), debe crear su propio Subproceso (tenga en cuenta que tener múltiples subprocesos de UI generalmente es una mala idea). Además, si desea mantener un objeto que es propiedad de un solo hilo y solo puede ser usado por ese hilo, es mucho más fácil crear explícitamente una instancia de Thread para que pueda verificar fácilmente si el código que intenta usarlo se está ejecutando. en el hilo correcto.
ThreadPool
ThreadPool es una envoltura alrededor de un grupo de subprocesos mantenidos por el CLR. ThreadPool no te da ningún control en absoluto; puede enviar trabajo para ejecutar en algún momento, y puede controlar el tamaño de la agrupación, pero no puede configurar nada más. Ni siquiera se puede decir cuándo el grupo comenzará a ejecutar el trabajo que le envíe.
Usar ThreadPool evita la sobrecarga de crear demasiados hilos. Sin embargo, si envía demasiadas tareas de larga duración al conjunto de subprocesos, puede llenarse, y el trabajo posterior que envíe puede terminar esperando que finalicen los elementos anteriores de larga duración. Además, ThreadPool no ofrece ninguna forma de averiguar cuándo se ha completado un elemento de trabajo (a diferencia de Thread.Join ()), ni una forma de obtener el resultado. Por lo tanto, ThreadPool se utiliza mejor para operaciones cortas donde la persona que llama no necesita el resultado.
Tarea
Finalmente, la clase de tareas de la biblioteca paralela de tareas ofrece lo mejor de ambos mundos. Al igual que ThreadPool, una tarea no crea su propio hilo de sistema operativo. En su lugar, las tareas son ejecutadas por un TaskScheduler; el programador predeterminado simplemente se ejecuta en ThreadPool.
A diferencia del ThreadPool, Task también le permite saber cuándo finaliza, y (a través del Task genérico) devolver un resultado. Puede llamar a ContinueWith () en una tarea existente para que ejecute más código una vez que la tarea finalice (si ya está terminada, se ejecutará la devolución de llamada de inmediato). Si la tarea es genérica, ContinueWith () le pasará el resultado de la tarea, permitiéndole ejecutar más código que lo utiliza.
También puede esperar sincrónicamente a que finalice una tarea llamando a Wait () (o, para una tarea genérica, obteniendo la propiedad Result). Al igual que Thread.Join (), esto bloqueará el subproceso de llamada hasta que la tarea finalice. Sincronizar la espera de una tarea suele ser una mala idea; evita que el subproceso que realiza la llamada realice cualquier otro trabajo, y también puede ocasionar interbloqueos si la tarea termina esperando (incluso de forma asíncrona) el subproceso actual.
Como las tareas aún se ejecutan en ThreadPool, no deben usarse para operaciones de larga ejecución, ya que aún pueden llenar el grupo de subprocesos y bloquear nuevos trabajos. En su lugar, Task proporciona una opción LongRunning, que le indicará a TaskScheduler que gire un nuevo hilo en lugar de ejecutarse en ThreadPool.
Todas las API de concurrencia de alto nivel más nuevas, incluidos los métodos Parallel.For * (), PLINQ, C # 5 y métodos asíncronos modernos en el BCL, se basan en la Tarea.
Conclusión
La conclusión es que la tarea casi siempre es la mejor opción; proporciona una API mucho más potente y evita el desperdicio de subprocesos del sistema operativo.
Las únicas razones para crear explícitamente sus propios subprocesos en el código moderno son establecer opciones por subproceso o mantener un subproceso persistente que necesita mantener su propia identidad.
Thread
es un concepto de nivel inferior: si está iniciando un subproceso directamente, sabe que será un subproceso separado, en lugar de ejecutarse en el grupo de subprocesos, etc.
Task
embargo, la Task
es más que una simple abstracción de "dónde ejecutar un código", en realidad es solo "la promesa de un resultado en el futuro". Así como algunos ejemplos diferentes:
-
Task.Delay
no necesita ningún tiempo de CPU real; Es como configurar un temporizador para que funcione en el futuro. - Una tarea devuelta por
WebClient.DownloadStringTaskAsync
no tomará mucho tiempo de CPU localmente; representa un resultado que probablemente pase la mayor parte de su tiempo en la latencia de la red o el trabajo remoto (en el servidor web) - Una tarea devuelta por
Task.Run()
realmente dice "Quiero que ejecutes este código por separado"; El hilo exacto en el que se ejecuta ese código depende de una serie de factores.
Tenga en cuenta que la abstracción de la Task<T>
es fundamental para el soporte asíncrono en C # 5.
En general, le recomiendo que use la abstracción de nivel superior siempre que pueda: en el código C # moderno, rara vez debería comenzar explícitamente su propio hilo.