Aquí hay un ejemplo completamente trabajado basado en la respuesta más votada, que es:
int timeout = 1000;
var task = SomeOperationAsync();
if (await Task.WhenAny(task, Task.Delay(timeout)) == task) {
// task completed within timeout
} else {
// timeout logic
}
La principal ventaja de la implementación en esta respuesta es que se han agregado genéricos, por lo que la función (o tarea) puede devolver un valor. Esto significa que cualquier función existente se puede incluir en una función de tiempo de espera, por ejemplo:
Antes de:
int x = MyFunc();
Después:
// Throws a TimeoutException if MyFunc takes more than 1 second
int x = TimeoutAfter(MyFunc, TimeSpan.FromSeconds(1));
Este código requiere .NET 4.5.
using System;
using System.Threading;
using System.Threading.Tasks;
namespace TaskTimeout
{
public static class Program
{
/// <summary>
/// Demo of how to wrap any function in a timeout.
/// </summary>
private static void Main(string[] args)
{
// Version without timeout.
int a = MyFunc();
Console.Write("Result: {0}\n", a);
// Version with timeout.
int b = TimeoutAfter(() => { return MyFunc(); },TimeSpan.FromSeconds(1));
Console.Write("Result: {0}\n", b);
// Version with timeout (short version that uses method groups).
int c = TimeoutAfter(MyFunc, TimeSpan.FromSeconds(1));
Console.Write("Result: {0}\n", c);
// Version that lets you see what happens when a timeout occurs.
try
{
int d = TimeoutAfter(
() =>
{
Thread.Sleep(TimeSpan.FromSeconds(123));
return 42;
},
TimeSpan.FromSeconds(1));
Console.Write("Result: {0}\n", d);
}
catch (TimeoutException e)
{
Console.Write("Exception: {0}\n", e.Message);
}
// Version that works on tasks.
var task = Task.Run(() =>
{
Thread.Sleep(TimeSpan.FromSeconds(1));
return 42;
});
// To use async/await, add "await" and remove "GetAwaiter().GetResult()".
var result = task.TimeoutAfterAsync(TimeSpan.FromSeconds(2)).
GetAwaiter().GetResult();
Console.Write("Result: {0}\n", result);
Console.Write("[any key to exit]");
Console.ReadKey();
}
public static int MyFunc()
{
return 42;
}
public static TResult TimeoutAfter<TResult>(
this Func<TResult> func, TimeSpan timeout)
{
var task = Task.Run(func);
return TimeoutAfterAsync(task, timeout).GetAwaiter().GetResult();
}
private static async Task<TResult> TimeoutAfterAsync<TResult>(
this Task<TResult> task, TimeSpan timeout)
{
var result = await Task.WhenAny(task, Task.Delay(timeout));
if (result == task)
{
// Task completed within timeout.
return task.GetAwaiter().GetResult();
}
else
{
// Task timed out.
throw new TimeoutException();
}
}
}
}
Advertencias
Habiendo dado esta respuesta, generalmente no es una buena práctica tener excepciones en su código durante la operación normal, a menos que tenga que:
- Cada vez que se lanza una excepción, es una operación extremadamente pesada,
- Las excepciones pueden ralentizar su código en un factor de 100 o más si las excepciones están en un circuito cerrado.
Solo use este código si no puede alterar absolutamente la función que está llamando, por lo que se agota después de un tiempo específico TimeSpan
.
Esta respuesta solo es aplicable cuando se trata de bibliotecas de bibliotecas de terceros que simplemente no puede refactorizar para incluir un parámetro de tiempo de espera.
Cómo escribir código robusto
Si desea escribir código robusto, la regla general es esta:
Cada operación que podría bloquearse indefinidamente, debe tener un tiempo de espera.
Si no observa esta regla, su código eventualmente golpeará una operación que falla por alguna razón, luego se bloqueará indefinidamente y su aplicación se bloqueará permanentemente.
Si hubo un tiempo de espera razonable después de un tiempo, entonces su aplicación se colgaría durante un período de tiempo extremo (por ejemplo, 30 segundos) y luego mostraría un error y continuaría su camino feliz, o volvería a intentarlo.