Este dilema se puede abordar de variadas formas, pero nosotros te enseñamos la solución más completa para nosotros.
Solución:
Thread
es un concepto de nivel inferior: si está iniciando directamente un hilo, saber será un subproceso separado, en lugar de ejecutarse en el grupo de subprocesos, etc.
Task
Sin embargo, 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”. Entonces, como algunos ejemplos diferentes:
Task.Delay
no necesita tiempo de CPU real; es como configurar un temporizador para que suene 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 del tiempo en latencia de red o trabajo remoto (en el servidor web) - Una tarea devuelta por
Task.Run()
De Verdad es diciendo “Quiero que ejecutes este código por separado”; el hilo exacto en el que se ejecuta ese código depende de varios factores.
Tenga en cuenta que el Task
La abstracción es fundamental para el soporte asíncrono en C # 5.
En general, le recomiendo que utilice la abstracción de nivel superior siempre que pueda: en el código C # moderno, rara vez debería necesitar iniciar explícitamente su propio hilo.
Fuente
Hilo
El subproceso representa un subproceso real a nivel del sistema operativo, con su propia pila y recursos del kernel. (técnicamente, una implementación de CLR podría utilizar fibras en su lugar, pero ningún CLR existente hace esto) Thread permite el mayor grado de control; puede Abortar () o Suspender () o Reanudar () un hilo (aunque esta es una muy mala idea), puede observar su estado y puede establecer propiedades a nivel de hilo como el tamaño de la pila, el estado del apartamento o la cultura.
El problema con Thread es que los subprocesos del sistema operativo son costosos. Cada subproceso que tiene consume una cantidad no trivial de memoria para su pila y agrega una sobrecarga adicional de CPU a medida que el contexto del procesador cambia entre subprocesos. En cambio, es mejor tener un pequeño grupo de subprocesos que ejecuten su código a medida que el trabajo esté disponible.
Hay momentos en los que no hay un hilo alternativo. Si necesita especificar el nombre (con fines de depuración) o el estado del apartamento (para mostrar una interfaz de usuario), debe crear su propio subproceso (tenga en cuenta que tener varios subprocesos de la interfaz de usuario generalmente es una mala idea). Además, si desea mantener un objeto que es propiedad de un solo hilo y solo puede ser utilizado 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 un contenedor alrededor de un grupo de subprocesos mantenidos por CLR. ThreadPool no le da ningún control en absoluto; puede enviar trabajo para que se ejecute en algún momento y puede controlar el tamaño del grupo, pero no puede configurar nada más. Ni siquiera puede saber cuándo el grupo comenzará a ejecutar el trabajo que le envíe.
El uso de ThreadPool evita la sobrecarga de crear demasiados hilos. Sin embargo, si envía demasiadas tareas de ejecución prolongada al grupo de subprocesos, puede llenarse y el trabajo posterior que envíe puede terminar esperando a que finalicen los elementos anteriores de ejecución prolongada. 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 en las que la persona que llama no necesita el resultado.
Tarea
Finalmente, la clase Task de Task Parallel Library ofrece lo mejor de ambos mundos. Al igual que ThreadPool, una tarea no crea su propio hilo de SO. En cambio, las tareas son ejecutadas por un TaskScheduler; el planificador predeterminado simplemente se ejecuta en ThreadPool.
A diferencia de ThreadPool, Task también le permite saber cuándo finaliza y (a través de la Task genérica) devolver un resultado. Puede llamar a ContinueWith () en una tarea existente para que ejecute más código una vez que finalice la tarea (si ya ha finalizado, ejecutará la devolución de llamada inmediatamente). Si la tarea es genérica, ContinueWith () le pasará el resultado de la tarea, lo que le permitirá ejecutar más código que lo use.
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 hilo de llamada hasta que finalice la tarea. Esperar sincrónicamente una tarea suele ser una mala idea; evita que el subproceso que realiza la llamada haga cualquier otro trabajo y también puede dar lugar a interbloqueos si la tarea termina esperando (incluso de forma asíncrona) al subproceso actual.
Dado que las tareas aún se ejecutan en ThreadPool, no deben usarse para operaciones de ejecución prolongada, ya que aún pueden llenar el grupo de subprocesos y bloquear el trabajo nuevo. En cambio, Task proporciona una opción LongRunning, que le indicará al TaskScheduler que active un nuevo hilo en lugar de ejecutarlo en ThreadPool.
Todas las API de simultaneidad de alto nivel más nuevas, incluidos los métodos Parallel.For * (), PLINQ, C # 5 await y los métodos asíncronos modernos en BCL, se basan en Task.
Conclusión
La conclusión es que Task es casi siempre 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.
Usualmente escuchas La tarea es un concepto de nivel superior que el hilo… y eso es lo que significa esta frase:
-
No puede usar Abort / ThreadAbortedException, debe admitir la cancelación de eventos en su “código comercial” de forma periódica.
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 esta bandera). Por la misma razónThread.Sleep(delay)
la llamada debe ser reemplazada porTask.Delay(delay, token)
llamada (pasando el token dentro para tener la posibilidad de interrumpir el retraso). -
No hay hilos
Suspend
yResume
Funcionalidad de métodos con tareas. La instancia de la tarea no se puede reutilizar cualquiera. -
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 / secundarias
//StartNew - starts task immediately, parent ends whith child var parent = Task.Factory.StartNew (() => var child = Task.Factory.StartNew(() => //... ); , TaskCreationOptions.AttachedToParent );
-
Por lo tanto, el hilo del sistema está completamente oculto a la tarea, pero aún así 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 grupo de subprocesos bajo el capó de la ejecución paralela de tareas. Puede haber diferentes estrategias para que el hilo obtenga nuevas tareas para ejecutar. Otro recurso compartido Programador de tareas se preocupa por eso. Algunos problemas que Programador de tareas resuelve 1) prefiere ejecutar la tarea y su conitnuación en el mismo hilo minimizando el costo de cambio – 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 Preferir imparcialidad 3) distribución más eficaz de tareas entre subprocesos inactivos en función del “conocimiento previo de la actividad de las 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 ejecuten en un hilo de forma sincrónica. Para expresar la ejecución de código paralelo, se pueden usar abstracciones más altas (que las tareas):
Parallel.ForEach
,PLINQ
,Dataflow
. -
Las tareas están integradas con las funciones async / await de C #, también conocidas como Modelo de promesa, por ejemplo, allí
requestButton.Clicked += async (o, e) => ProcessResponce(await client.RequestAsync(e.ResourceName));
la ejecución declient.RequestAsync
no bloqueará el hilo de la interfaz de usuario. Importante: debajo del capóClicked
La llamada de delegado es absolutamente regular (todo el subproceso lo realiza el compilador).
Eso es suficiente para tomar una decisión. Si necesita admitir la funcionalidad Cancelar para llamar a la API heredada que tiende a colgarse (por ejemplo, una conexión sin tiempo de espera) y para este caso es compatible con Thread.Abort (), o si está creando cálculos en segundo plano de subprocesos múltiples y desea optimizar el cambio entre subprocesos mediante Suspender / Reanudar , eso significa administrar la ejecución paralela manualmente: quédese con Thread. De lo contrario, vaya a Tareas porque le permitirán manipular fácilmente grupos de ellas, están integradas en el lenguaje y hacen que los desarrolladores sean más productivos: Task Parallel Library (TPL).
Agradecemos que quieras estimular nuestro cometido añadiendo un comentario y dejando una puntuación te lo agradecemos.