CodeGym /Cursos /C# SELF /Asincronía vs. Multihilo ( async/await y Thread)

Asincronía vs. Multihilo ( async/await y Thread)

C# SELF
Nivel 59, Lección 1
Disponible

1. Introducción

En programación, los recién llegados (e incluso desarrolladores experimentados) a menudo se confunden entre dos conceptos parecidos pero en realidad distintos: multihilo y asincronía. En entrevistas les gusta hacer esta pregunta para ver si la persona entiende la diferencia, porque de eso depende directamente cómo escribir código rápido y responsivo.

Vamos a ver cuál es la trampa.

Multihilo: cuando trabajan muchas manos

Multihilo es la organización del trabajo del programa usando varios hilos. Un hilo es una hebra de ejecución, una "pista" separada por la que el procesador ejecuta las instrucciones del programa. Un proceso (por ejemplo, nuestra aplicación .NET) puede lanzar varios hilos para que distintas tareas se ejecuten simultáneamente.

Ejemplo de la vida real: Eres el gestor de proyecto que asigna distintas tareas a varios colegas, y todas esas tareas se hacen al mismo tiempo. Por ejemplo, uno escribe el informe, otro llama al cliente, el tercero prepara la presentación.

Idea clave del multihilo: las tareas ocurren realmente en paralelo (o cuasi-paralelo, si el procesador es uno, gracias al cambio rápido de contexto).

Asincronía: la habilidad de no quedarse parado

Asincronía es organizar el código para que el programa pueda hacer otra cosa mientras espera la finalización de una operación larga (por ejemplo, la respuesta de Internet o la lectura de un archivo). El código asincrónico no necesariamente usa varios hilos. Simplemente no bloquea la ejecución de tu programa durante la espera de alguna operación.

Ejemplo de la vida real: En vez de quedarte junto a la cafetera mirando cómo cae el café, le das la tarea a la cafetera y tú te ocupas de otra cosa (respondes e-mails, lees noticias), y recoges tu café cuando ya está listo.

Idea clave de la asincronía: no quedarse parado mientras esperas tareas "largas", sino hacer algo útil.

2. ¿En qué se diferencian?

Con frecuencia asincronía y multihilo se usan juntos, y eso aumenta la confusión. Pero en realidad estas técnicas responden a preguntas distintas:

  • Multihilo sirve para paralelizar trabajo realmente, usando los procesadores/núcleos disponibles.
  • Asincronía sirve para organizar inteligentemente la espera de recursos (I/O de red, disco, etc.), sin bloquear el hilo.

Se pueden combinar, pero no tienen que estar necesariamente relacionadas.

Visualización rápida

Multihilo (Threads) Asincronía (Async)
¿Cuándo aplicar? Cuando la tarea consume CPU (CPU-bound: cálculos, render, procesamiento de arrays) Cuando la tarea espera un evento (I/O-bound: red, disco, base de datos)
¿Qué hace el código? Hace trabajar al procesador, lanza varios hilos, paraleliza realmente Cuando espera datos ("está inactivo"), libera el hilo — éste puede ocuparse de otra cosa
¿Qué gestiona? La cantidad de hilos ejecutándose simultáneamente Quién está ocupado/libre y qué hacer cuando la operación termine
Ejemplo típico Compresión de video, renderizado de imagen Descarga de archivo, petición HTTP a un servidor

Ejemplo 1: Multihilo — contamos rápido

Supongamos que tenemos una tarea pesada: sumar números grandes.


void ComputeSum(long start, long end)
{
    long sum = 0;
    for (long i = start; i <= end; i++)
    {
        sum += i;
    }
    Console.WriteLine($"La suma de {start} a {end} = {sum}");
}

// Lanzamos tres tareas al mismo tiempo — cada una calcula su parte del trabajo
Thread t1 = new Thread(() => ComputeSum(1, 1000_000_000));
Thread t2 = new Thread(() => ComputeSum(1000_000_001, 2000_000_000));
Thread t3 = new Thread(() => ComputeSum(2000_000_001, 3000_000_000));

t1.Start();
t2.Start();
t3.Start();

// Esperamos a que todos los hilos terminen
t1.Join();
t2.Join();
t3.Join();

¿Por qué aquí hilos?
Porque el trabajo es CPU-bound. Los hilos realmente cargan el procesador, y si tienes una máquina multicore — el trabajo irá más rápido.

Ejemplo 2: Asincronía — esperando respuesta del servidor

La red es lenta, y mientras esperamos la respuesta del servidor, el hilo puede estar "libre".


// Descargamos una página de forma asincrónica, el hilo no se bloquea
async Task DownloadPageAsync()
{
    using HttpClient client = new HttpClient();
    string html = await client.GetStringAsync("https://dotnet.microsoft.com/");
    Console.WriteLine(html.Length);
}

¿Por qué asincronía aquí?
Damos al sistema la orden "empieza la descarga" y no bloqueamos el hilo, sino que esperamos la notificación cuando lleguen los datos.

3. ¿Asincronía sin multihilo: mito o realidad?

Pregunta: ¿Todo código asincrónico implica siempre lanzar un nuevo hilo?
Respuesta: ¡No! A menudo la asincronía no requiere hilos adicionales.

Por ejemplo, cuando haces await file.ReadAsync(...), .NET lanza la operación de forma asincrónica a nivel del SO, y el hilo que hizo la llamada queda "libre" y vuelve al pool de hilos. Cuando la operación termina, algún hilo libre del pool continuará la ejecución de tu tarea.

  • Si en vez de asincronía usaras la versión síncrona (file.Read(...)) — el hilo simplemente esperaría la finalización, sin hacer nada.
  • El código asincrónico dice: "Procesador, mientras esperamos — ¡haz otra cosa!"

Ilustración importante:


// El "hilo" no se bloquea, sólo esperamos a que la operación esté lista
await Task.Delay(1000); // Simplemente esperar un segundo — ¡no usa procesador!

Multihilo sin asincronía

Sucede que paralelizar el trabajo sólo con hilos tiene sentido: cálculos pesados, bucles grandes de procesamiento de datos, etc. En ese caso la asincronía no ayuda a acelerar los propios cálculos, porque el procesador ya estará al 100%.

Clásico: procesamiento de archivos grandes


// Este código realmente carga el procesador — la asincronía no ayudará.
void CalculateHash(string file)
{
    byte[] data = File.ReadAllBytes(file); // ¡síncrono!
    // Calculamos el hash...
}

Si quieres acelerar — lanza varios hilos, cada uno trabaja con su propio archivo.

4. ¿Cómo se ve esto en tu aplicación?

Operaciones asincrónicas (await)

En nuestra app de aprendizaje puedes añadir carga asincrónica de datos. Por ejemplo, pedir el tipo de cambio o el tiempo — mejor hacerlo de forma asincrónica.


async Task GetWeatherAsync(string city)
{
    using HttpClient client = new HttpClient();
    string json = await client.GetStringAsync($"https://api.weather.com/{city}");
    // Continuamos cuando llega la respuesta
    Console.WriteLine($"Tiempo en {city}: {json}");
}

¿Qué pasa bajo el capó?

La llamada await "corta" tu método en dos partes:

  • Lanzas la operación asincrónica — el hilo del SO se "libera" y puede hacer otras tareas.
  • Cuando llegan los datos — la ejecución del método continúa en alguno de los hilos libres del pool.

Multihilo para cálculos complejos

En nuestro ejemplo con la calculadora (si empieza a procesar grandes arrays de datos) — aquí tiene sentido lanzar cálculos en hilos separados.


// Dividimos la tarea grande en partes pequeñas, cada una calcula su parte
List<Thread> threads = new List<Thread>();
for (int i = 0; i < 4; i++)
{
    int rangeStart = i * 1000000;
    int rangeEnd = (i + 1) * 1000000 - 1;
    Thread t = new Thread(() => ComputeSum(rangeStart, rangeEnd));
    threads.Add(t);
    t.Start();
}
// Esperamos a que todos los hilos terminen
foreach (Thread t in threads) t.Join();

5. Errores típicos y matices al trabajar con asincronía

Error №1: usar async "para velocidad".
Un error muy común es pensar que async acelera la ejecución del código. No es así. La asincronía trata sobre la capacidad de respuesta, no sobre la velocidad.

Si la tarea es CPU-bound — la asincronía no la hará más rápida.
Si la tarea es I/O-bound (red, disco) — la asincronía es útil porque el hilo no queda inactivo y puede hacer otro trabajo.

Error №2: bloquear hilos con Wait() y Result.
En código asincrónico está totalmente prohibido llamar Wait() o la propiedad Result sobre Tasks. Esto casi siempre lleva a bloqueo de hilos y deadlocks.


// ¡Mal! Bloqueará el hilo y causará problemas
var result = GetDataAsync().Result;

async Task<string> GetDataAsync() { /* ... */ return "data"; }

El enfoque correcto es usar await y no bloquear el hilo.

Error №3: asincronía y UI.
En aplicaciones gráficas (WPF, WinForms) el principal problema es no congelar el hilo de UI. Si en el hilo principal lanzas una operación larga o bloqueante, toda la aplicación "se quedará colgada". La asincronía soluciona esto: el trabajo pesado se hace en background y la interfaz sigue siendo responsiva.

Error №4: falta de convención de nombres para métodos asincrónicos.
Si no añades el sufijo Async a los métodos asincrónicos, es fácil confundir cuál método es síncrono y cuál asincrónico. Esto lleva a bloqueos accidentales y errores al invocar. Siempre nombra los métodos asincrónicos con Async al final.

2
Tarea
C# SELF, nivel 59, lección 1
Bloqueada
Ejemplo de una operación asíncrona simple
Ejemplo de una operación asíncrona simple
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION