Dos herramientas para "a lo largo del tiempo"#
Ya conoces las corrutinas: reparten trabajo en varios fotogramas con yield. C# tiene otra vía, async/await, pensada para operaciones asíncronas (esperar red, cargar archivos). En Unity conviven, y elegir bien evita muchos dolores.
Recordatorio: corrutinas#
IEnumerator Secuencia()
{
Debug.Log("Empieza");
yield return new WaitForSeconds(2f);
Debug.Log("2 segundos después");
}
StartCoroutine(Secuencia());Integradas en Unity, atadas al MonoBehaviour (se paran si el objeto se desactiva) y al bucle de juego.
async/await#
using System.Threading.Tasks;
using UnityEngine;
async void Secuencia()
{
Debug.Log("Empieza");
await Task.Delay(2000); // 2000 ms
Debug.Log("2 segundos después");
}await "pausa" el método hasta que la tarea termina, sin bloquear el juego, y luego continúa. Es el modelo estándar de C# para asincronía.
Unity 6 trae la clase Awaitable, un tipo async nativo de Unity que se integra con el bucle de juego y el hilo principal, y genera menos basura que Task. Permite cosas como await Awaitable.WaitForSecondsAsync(2f) o await Awaitable.NextFrameAsync(). Es la forma moderna recomendada para async en Unity, sin depender de librerías externas.
async Awaitable Ejemplo()
{
await Awaitable.WaitForSecondsAsync(2f);
Debug.Log("Listo, en el hilo principal");
}Comparativa#
| Corrutinas | async/await (Task / Awaitable) | |
|---|---|---|
| Integración con Unity | Total (nativa) | Task: parcial · Awaitable: nativa (Unity 6) |
| Se cancela al desactivar el objeto | Sí, automático | No (Task); hay que gestionarlo |
| Valor de retorno | No (solo IEnumerator) | Sí (Task<T>, Awaitable<T>) |
| Manejo de errores | Manual/torpe | try/catch natural |
| Operaciones de red/archivos | Incómodo | Su terreno natural |
| Riesgo de hilos | Bajo | Alto si no cuidas el hilo principal |
| Genera basura (GC) | Algo | Task: más · Awaitable: menos |
La trampa gorda: el hilo principal#
No puedes tocar transform, GameObject, la mayoría de la API de Unity, desde otro hilo: peta. Algunas tareas async (según cómo se configuren) pueden continuar en un hilo distinto. Con corrutinas esto no pasa (siempre en el hilo principal). Con async/await, usa Awaitable (que vuelve al hilo principal) o ten mucho cuidado. Es la causa nº1 de errores raros al pasarse a async sin saber.
Otra trampa: async void#
Un async void no se puede esperar ni capturar sus errores: si algo falla, la excepción se pierde de forma silenciosa. Usa async Task o async Awaitable para poder esperarlos y manejar errores. async void solo es aceptable en manejadores de eventos.
Cuándo usar cada uno#
- ¿Secuencia de juego, esperas, efectos escalonados, atado a un objeto? → corrutina (simple y segura)
- ¿Operación asíncrona real (red, cargar/guardar archivos, web)? → async/await
- ¿Necesitas devolver un valor o try/catch limpio? → async/await
- ¿Estás en Unity 6 y quieres async integrado con menos basura? → Awaitable
- ¿Proyecto grande con mucho async y máximo rendimiento? → considera UniTask (librería externa muy popular)
Cancelación#
Las corrutinas se paran solas al desactivar el objeto. Con async, debes usar un CancellationToken para poder cancelar (por ejemplo, si el objeto se destruye a mitad de una espera). Ignorarlo provoca continuar código sobre objetos ya destruidos.
Errores frecuentes#
- Tocar API de Unity desde un hilo que no es el principal (usa
Awaitable). async voidque se traga las excepciones.- No cancelar tareas async cuando el objeto se destruye (usar objetos muertos).
- Usar async para una simple espera de juego donde una corrutina es más simple y segura.
Ponte a prueba#
Resumen#
Corrutinas: simples, nativas, atadas al objeto y al hilo principal → ideales para secuencias de juego y esperas. async/await: para asincronía real (red, archivos), con valores de retorno y try/catch, pero cuidando el hilo principal (usa el nuevo Awaitable de Unity 6) y la cancelación. Evita async void. Regla rápida: juego → corrutina; E/S asíncrona → async con Awaitable.