A3.B Resultados, errores y esperas¶
Resultados futuros: Task<T>¶
Un Task<int> es una promesa: «dentro de un rato tendrás un int, o un error». Con await se obtiene el resultado, y si la tarea falló, await vuelve a lanzar la excepción en el sitio donde esperas, de modo que se captura con un try/catch normal. No hay un mecanismo de errores aparte.
Un ejemplo: tres consultas y un error¶
async Task<int> Consulta(string nombre, int ms, bool falla = false)
{
await Task.Delay(ms);
if (falla)
{
throw new InvalidOperationException($"falló {nombre}");
}
Console.WriteLine($"{nombre} responde");
return ms;
}
// 1. Una tarea con resultado
int valor = await Consulta("a", 30);
Console.WriteLine($"resultado: {valor}");
// 2. Varias a la vez, todas correctas: WhenAll devuelve un array con los resultados, EN EL ORDEN en que las pasaste
int[] todos = await Task.WhenAll(Consulta("b", 60), Consulta("c", 20));
Console.WriteLine($"resultados: {string.Join(", ", todos)}");
// 3. Si una falla, await lanza la excepción de la primera que falló
var tareas = new[] { Consulta("d", 30), Consulta("e", 10, falla: true), Consulta("f", 50, falla: true) };
try
{
await Task.WhenAll(tareas);
}
catch (InvalidOperationException e)
{
Console.WriteLine($"error recogido: {e.Message}");
}
// Pero se conservan TODAS: cada tarea sabe cómo terminó
foreach (var t in tareas)
{
Console.WriteLine($" tarea {(t.IsFaulted ? "con error" : "correcta")}");
}
Salida:
a responde
resultado: 30
c responde
b responde
resultados: 60, 20
d responde
error recogido: falló e
tarea correcta
tarea con error
tarea con error
Tres cosas que conviene saber:
- El orden de
WhenAlles el de la lista que le pasas, no el de llegada: enb, cel resultado es60, 20aunquectermine antes. - Si varias fallan,
await Task.WhenAll(...)lanza solo la primera excepción. Para ver todas, mira cada tarea (t.IsFaulted,t.Exception) como hace el bucle final.t.Exceptiones unaAggregateExceptionque agrupa las excepciones de la tarea. - Una tarea que falla no detiene a las demás: las otras dos siguen y terminan.
Límites de tiempo y cancelación¶
Esperar sin límite es peligroso: un servidor que no responde deja el programa colgado. Hay tres herramientas:
| Quiero... | Herramienta |
|---|---|
| Dejar de esperar pasado un tiempo | tarea.WaitAsync(TimeSpan) → lanza TimeoutException |
| Pedir a la tarea que pare | CancellationToken (de un CancellationTokenSource) → la tarea lanza OperationCanceledException |
| Repetir una operación que falla a veces | Un bucle con try/catch (o la biblioteca Polly en proyectos reales) |
async Task<int> Consulta(int ms, CancellationToken token = default)
{
await Task.Delay(ms, token);
return 42;
}
// 1. Límite de tiempo: WaitAsync lanza TimeoutException
try
{
await Consulta(300).WaitAsync(TimeSpan.FromMilliseconds(100));
}
catch (TimeoutException)
{
Console.WriteLine("tiempo agotado");
}
// 2. Cancelación: el token avisa a la tarea de que debe parar
using var cancelacion = new CancellationTokenSource(TimeSpan.FromMilliseconds(100));
try
{
await Consulta(300, cancelacion.Token);
}
catch (OperationCanceledException)
{
Console.WriteLine("tarea cancelada");
}
// 3. Reintentos: repetir una operación que falla a veces
int llamadas = 0;
async Task<string> Inestable()
{
await Task.Delay(5);
if (++llamadas < 3)
{
throw new InvalidOperationException("fallo temporal");
}
return "listo";
}
for (int intento = 1; intento <= 3; intento++)
{
try
{
Console.WriteLine($"intento {intento}: {await Inestable()}");
break;
}
catch (InvalidOperationException)
{
Console.WriteLine($"intento {intento} falló");
}
}
Salida:
tiempo agotado
tarea cancelada
intento 1 falló
intento 2 falló
intento 3: listo
La diferencia entre WaitAsync y la cancelación es importante: WaitAsync solo deja de esperar; la tarea sigue ejecutándose por detrás. Cancelar es pedirle a la tarea que pare, y la tarea tiene que cooperar comprobando el token (como hace Task.Delay(ms, token)). Para que una tarea sea cancelable, acepta un CancellationToken y pásalo a todas las operaciones que haga.
Errores frecuentes¶
| Error | Cómo evitarlo |
|---|---|
| Esperar sin límite una respuesta externa | WaitAsync o un CancellationTokenSource con tiempo |
Pensar que WaitAsync cancela la tarea |
Solo deja de esperarla; para pararla, usa un token |
| Capturar solo una excepción de varias tareas | Revisar cada tarea (IsFaulted, Exception) |
| Reintentar sin límite | Fijar un máximo de intentos (y, en la vida real, esperar un poco entre ellos) |
.Result o .Wait() en vez de await |
Siempre await; bloquean y envuelven el error en AggregateException |
Para practicar¶
Los ejercicios A3.3 y A3.4 usan límites de tiempo y reintentos. Para ver la misma idea escrita en los otros lenguajes: Pasar de uno a otro.