¿Cómo funciona la asincronía? JS & C# version
25-06-2025
Durante los últimos años he estado trabajando con TypeScript en mi día a día, generalmente haciendo uso de la asincronía para muchas cosas. Recientemente, debido a un cambio de proyecto, he empezado a trabajar en C# aplicando esto que había aprendido en JavaScript (JS) y TypeScript (TS) de forma natural, ya que a simple vista parece que lo que en JS es una Promise, en C# es una Task pero ¿realmente funcionan igual en ambos lenguajes?
Este artículo ha salido de la curiosidad de ver cómo gestionan estos lenguajes la asincronía y comprobar si, aunque al escribirlas en código se parezcan por su estructura, realmente se gestiona igual.
¿Qué es la asincronía?
La asincronía permite que el programa pueda continuar ejecutándose mientras espera que una tarea termine, como una llamada a una API, una operación de lectura de archivos o una espera por tiempo.
Es una característica fundamental en los lenguajes de programación modernos. Permite ejecutar tareas sin bloquear el flujo principal de la aplicación, lo que resulta esencial para mantener una experiencia de uso fluida y una alta eficiencia en la ejecución de procesos.
JavaScript: Event Loop, Call Stack y Microtask Queue
JavaScript es un lenguaje single-threaded, es decir, tiene un solo hilo de ejecución. Para manejar operaciones asincrónicas, utiliza una arquitectura basada en:
Call Stack
Es la pila donde se ejecuta el código sincrónico. Cada vez que se llama a una función, se añade a la pila. Cuando finaliza, se elimina.
Web APIs
Estas permiten delegar operaciones asincrónicas como setTimeout
, fetch, etc., al entorno del navegador, liberando el Call Stack.
Callback Queue (Macrotask Queue)
Aquí se almacenan tareas como los setTimeout
, setInterval
, y eventos DOM. Cuando el Call Stack está vacío, el Event Loop mueve una tarea de esta cola al stack.
Microtask Queue
Cola especial para tareas asincrónicas de alta prioridad, que incluye:
- Callbacks de Promesas (
.then()
,.catch()
,.finally()
) - Código
async
/await
queueMicrotask()
MutationObserver
El Event Loop trabaja con la Microtask Queue igual que con el Callback Queue, cuando el Call Stack está vacío, mueve una tarea al stack, con la diferencia de que se priorizan las microtasks.
Orden de ejecución:
- Ejecuta el código sincrónico
- Ejecuta todas las microtareas pendientes
- Ejecuta una tarea de la Callback Queue
Ejemplo:
console.log("Inicio"); setTimeout(() => console.log("setTimeout"), 0); Promise.resolve().then(() => console.log("Promise")); console.log("Fin");
Salida:
Inicio Fin Promise setTimeout
Promesas y Microtasks en profundidad
Las promesas utilizan la Microtask Queue, lo que les da prioridad sobre otras tareas asincrónicas. Esto garantiza un flujo de ejecución más predecible y controlado.
Ejemplo:
Promise.resolve().then(() => { console.log("Primera microtarea"); }); setTimeout(() => { console.log("Macrotarea (setTimeout)"); }, 0);
Salida:
Primera microtarea Macrotarea (setTimeout)
C#: ThreadPool y Task Parallel Library (TPL)
A diferencia de JavaScript, C# permite la ejecución multihilo de forma nativa. Para facilitar la asincronía y el paralelismo, ofrece la Task Parallel Library (TPL).
ThreadPool
Es un grupo de hilos reutilizables administrados por .NET. Cuando se crea una tarea, se toma un hilo disponible del ThreadPool sin tener que crear uno manualmente. Que .NET asigne estos hilos automáticamente no impide que se puedan gestionar manualmente si se quisiera.
Task y async/await
La TPL introduce Task
y el uso de async/await
para facilitar la escritura de código asincrónico y legible. Además de la gestión de las tareas y los hilos.
Ejemplo:
using System; using System.Net.Http; using System.Threading.Tasks; class Program { static async Task Main() { Console.WriteLine("Inicio"); var tarea = TrabajoLargoAsync(); Console.WriteLine("Haciendo otras cosas en el hilo principal..."); var resultado = await tarea; Console.WriteLine($"Resultado: {resultado}"); Console.WriteLine("Fin"); } static async Task<string> TrabajoLargoAsync() { Console.WriteLine("Iniciando trabajo largo..."); await Task.Delay(2000); // Simula una operación que tarda 2 segundos Console.WriteLine("Trabajo terminado."); return "Listo"; } }
Este código no bloquea el hilo principal mientras espera la respuesta HTTP.
Salida:
Inicio Iniciando trabajo largo... Haciendo otras cosas en el hilo principal... Trabajo terminado. Resultado: Listo Fin
Comparación entre JavaScript y C#
Característica | JavaScript | C# / .NET |
---|---|---|
Modelo de ejecución | Single-thread + Event Loop | Multi-threaded con ThreadPool |
Asincronía principal | Promesas, async/await | Task, async/await |
Colas de ejecución | Microtask y Callback Queue | TaskScheduler y ThreadPool |
Uso típico | Web, UI | Web, escritorio, servicios |
Conclusión
Tanto JavaScript como C# ofrecen mecanismos potentes para trabajar con asincronía, aunque usan paradigmas diferentes. Comprender estas diferencias permite escribir código más eficiente y adaptado al entorno en el que se ejecuta.
Para tareas reactivas el modelo basado en Event Loop de JavaScript es ideal. Para tareas paralelas intensivas y de backend, la TPL y el ThreadPool de C# brindan mayor control y escalabilidad.