CodeGym /Cursos /C# SELF /Comparación Task y ...

Comparación Task y Thread

C# SELF
Nivel 60 , Lección 1
Disponible

1. Introducción

Ha llegado el momento de entender: ¿en qué se diferencian fundamentalmente Task y Thread? ¿Por qué C# lleva años recomendando usar Task en lugar de gestionar hilos directamente? ¿En qué situaciones se puede seguir usando hilos manuales y cuándo es suficiente (y necesario) quedarse en el estilo de tareas?

Si sientes que las palabras "hilos" y "tareas" empiezan a mezclarse en algún rincón oscuro de tu mente y el corazón se acelera — no te preocupes, no estás solo. Incluso programadores experimentados a veces se confunden cuando se trata de paralelismo y asincronía.

Vamos a poner todo en su sitio. ¡Vamos!

Breve historia de la aparición de Task

En los buenos viejos tiempos (antes de .NET 4.0) la única forma evidente de ejecutar código en paralelo o "en segundo plano" era crear un nuevo hilo. Por ejemplo, new Thread(() => { ... }).Start(); Los hilos son geniales por su sencillez. Pero son terribles porque todo queda sobre tus hombros. Asignación de recursos, ciclo de vida, manejo de excepciones, sincronización, monitoreo, escalabilidad: todo eso es responsabilidad del desarrollador. Y, la verdad, ¡cuando se programa apetece ser más perezoso!

Todo cambió con la llegada de las tareas — Task — del espacio de nombres System.Threading.Tasks.Task. Una tarea no es un hilo. Es un concepto más abstracto y flexible. Describe un trabajo que debe hacerse en algún momento en el futuro, posiblemente en paralelo.

2. Thread — "Hilo desnudo"

Thread — es una unidad de ejecución de bajo nivel, que representa un trozo dedicado de recursos del sistema operativo (su propio stack, contexto de ejecución, etc.). Si creas un hilo manualmente, eres responsable de su inicio, finalización y de todos los detalles de su vida.

using System;
using System.Threading;

class Program
{
    static void Main()
    {
        Thread thread = new Thread(() => {
            Console.WriteLine("Hola desde el hilo!");
        });

        thread.Start();
        thread.Join(); // Esperamos a que termine el hilo
    }
}
  • Aquí creamos un hilo que ejecuta la lambda en su propio stack.
  • Tras iniciar el hilo llamamos a Join() para esperar a que termine.

¿Dónde está el truco?

  • Cada hilo ocupa memoria (stack, ~1 MB).
  • En .NET no es recomendable crear miles de hilos manualmente — el sistema sufrirá.
  • Si olvidas llamar a Join(), el hilo principal puede terminar antes que el hijo y el programa "se cortará".
  • Las excepciones dentro del hilo no salen por defecto — ¡hay que capturarlas explícitamente!
  • Si arrancas un hilo — cancelarlo "bonito" no es posible (no existe el método Stop()!).

3. Task — "Tareas de nueva generación"

Task — una abstracción más inteligente que representa "un trabajo que se hará en algún momento". Bajo el capó las tareas se ejecutan en pools de hilos (ThreadPool), lo cual es mucho más eficiente que inflar una gran cantidad de hilos. No gestionas su creación manualmente: el pool lo hace por ti, escalando el número de hilos según la carga.

using System;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        Task task = Task.Run(() =>
        {
            Console.WriteLine("Hola desde Task!");
        });

        await task; // Esperamos a que termine la tarea
    }
}
  • La tarea no garantiza ejecución en un hilo separado, pero normalmente se ejecuta en un hilo del pool.
  • Puedes esperar a que termine la tarea de la forma habitual (await en un método async o task.Wait() en código sincrónico).

4. ¿Cuál es la diferencia entre Task y Thread?

Desgranemos en qué se diferencian, para qué usarlos y qué trampas (no evidentes) hay.

Thread Task
Abstracción Hilo del SO Trabajo/Tarea (abstracción que puede usar un hilo)
Inicio A través de new Thread(...).Start() A través de Task.Run(...), Task.Factory.StartNew(...), métodos async
Control directo Sí (start, Join, prioridad, etc.) No, .NET se encarga del control
ThreadPool No, el hilo se crea siempre nuevo Sí, suele usar ThreadPool
Gestión de recursos Se asigna stack propio Recursos reutilizados por el pool
Escalabilidad Mal: ineficiente para 1000+ hilos Genial: miles de tareas = bien
Interacción Hilo separado desde el punto de vista del SO Puede continuar en el hilo actual o ejecutarse en ThreadPool
Excepciones Requiere captura explícita, si no pueden "desaparecer" Las excepciones se almacenan en la Task; se pueden capturar con await o .Wait()
Cancelación No hay forma estándar Sí, soporte vía CancellationToken
Resultado del trabajo Esperar con Join() await, .Wait(), .Result
Usar para Casos especiales — hilos UI, hilos de larga duración Casi todas las tareas de fondo/paralelas

5. ¿Cuándo usar qué?

¿Cuándo usar Thread?

Honestamente, en código .NET moderno raramente es necesario crear hilos manualmente. Aquí ejemplos cuando tiene sentido:

  • Necesitas un hilo que vaya a vivir muchísimo tiempo (por ejemplo, serialización de una señal en la radio, o procesamiento de datos desde hardware) y además es "especial": prioridad baja, cultura de ejecución distinta, nombre propio.
  • A veces para integrar con APIs de bajo nivel que requieren gestión manual de hilos.
  • En casos muy específicos, como planificadores de tareas personalizados.

En todos los demás casos — Task será la opción más correcta y moderna.

¿Cuándo usar Task?

Prácticamente siempre que necesites realizar trabajo "en segundo plano" o "en paralelo":

  • Cálculos en background que se pueden ejecutar en el pool de hilos (por ejemplo, procesamiento de una petición en servidor, parseo de un archivo, envío de correos).
  • Ejecutar operaciones asincrónicas (async/await) — el mecanismo devuelve Task o Task<T>.
  • Combinar tareas, manejar continuaciones (continuations), trabajar con cadenas de tareas.
  • Facilidad de cancelación, espera y recolección de resultados: Task soporta CancellationToken, se integra fácilmente con APIs modernas.
  • Operaciones asincrónicas de I/O: peticiones de red, acceso a archivos, bases de datos.

Comparación

Escenario Thread Task
Hilo de larga duración (p.ej. tu propio servicio) No
Ejecutar masivamente tareas cortas No
Operaciones I/O-asincrónicas (await) No
Combinación, cancelación, cadenas de tareas No
Ajuste fino de prioridad y cultura Sí (pero raro) No, sólo para tareas por defecto
Simple reparto de trabajo entre núcleos (CPU) A veces

6. Matices útiles

Task — ¡no siempre es un hilo!

La magia más poderosa: si usas Task para operaciones I/O-asincrónicas, ¡no se crea un hilo nuevo en absoluto! Todo "mágicamente" se va al limbo (IO Completion Ports u otros primitivos de la plataforma). El hilo queda libre mientras tu tarea espera algo externo: archivo, red, base de datos. De hecho, durante la espera ningún hilo está ocupado.

Task y asincronía (I/O-bound) — la magia de await

using System;
using System.Net.Http;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        // Descargamos asíncronamente el contenido de un sitio (I/O-bound)
        HttpClient client = new HttpClient();
        string data = await client.GetStringAsync("https://www.dotnetfoundation.org");
        Console.WriteLine($"Cantidad de caracteres recibidos: {data.Length}");
    }
}
  • Aquí la tarea (Task<string>) encapsula una operación I/O asíncrona.
  • El hilo no se bloquea — sigue trabajando, y cuando la descarga termina la ejecución del método continúa.
  • Crear un hilo manualmente para esta tarea sería completamente innecesario e ineficiente.

Task y ThreadPool

Cuando escribes Task.Run(...) o usas una API asíncrona (await algo), .NET normalmente usa un pool especial de hilos — ThreadPool. Es un conjunto de hilos creados de antemano que "esperan en el banquillo" y están listos para coger rápidamente cualquier trabajo que llegue. Si hay poca carga — los hilos están inactivos; si hay mucha carga — se crean hilos nuevos automáticamente, pero de forma razonable. Gracias a esto tus apps escalan con la cantidad de tareas sin crear una carga excesiva en el sistema.

Un hilo creado con new Thread es casi siempre un "habitante" separado del sistema — no vuelve al pool tras terminar, simplemente muere. Por eso Task es mucho más eficiente para paralelismo masivo.

7. Errores típicos y trampas

Si de repente te apetece ser un programador retro y escribir todo con hilos, te esperan aventuras maravillosas: fugas de memoria, sincronización compleja, imposibilidad de cancelar trabajo, hilos "colgados" (zombies), captura y manejo de errores mediante APIs especiales.

Lo más importante a recordar: "Task" es cómodo, seguro y moderno. En la gran mayoría de los casos al desarrollar en C# hoy en día no hay razones para volver al manejo manual de hilos.

2
Tarea
C# SELF, nivel 60, lección 1
Bloqueada
Ejecución de una tarea usando Task
Ejecución de una tarea usando Task
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION