Aqui está um exemplo completo, com base na resposta mais votada, que é:
int timeout = 1000;
var task = SomeOperationAsync();
if (await Task.WhenAny(task, Task.Delay(timeout)) == task) {
// task completed within timeout
} else {
// timeout logic
}
A principal vantagem da implementação nesta resposta é que os genéricos foram adicionados, para que a função (ou tarefa) possa retornar um valor. Isso significa que qualquer função existente pode ser agrupada em uma função de tempo limite, por exemplo:
Antes:
int x = MyFunc();
Depois de:
// Throws a TimeoutException if MyFunc takes more than 1 second
int x = TimeoutAfter(MyFunc, TimeSpan.FromSeconds(1));
Este código requer o .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();
}
}
}
}
Ressalvas
Tendo dado essa resposta, geralmente não é uma boa prática ter exceções lançadas em seu código durante a operação normal, a menos que você precise:
- Cada vez que uma exceção é lançada, é uma operação extremamente pesada,
- Exceções podem tornar seu código lento por um fator de 100 ou mais se as exceções estiverem em um loop restrito.
Somente use esse código se você absolutamente não puder alterar a função que está chamando, para que o tempo limite seja excedido após um determinado TimeSpan
.
Essa resposta é realmente aplicável apenas ao lidar com bibliotecas de bibliotecas de terceiros que você simplesmente não pode refatorar para incluir um parâmetro de tempo limite.
Como escrever código robusto
Se você deseja escrever um código robusto, a regra geral é esta:
Toda operação que pode potencialmente bloquear indefinidamente deve ter um tempo limite.
Se você não observar essa regra, seu código acabará atingindo uma operação que falhará por algum motivo, bloqueará indefinidamente e seu aplicativo será interrompido permanentemente.
Se houvesse um tempo limite razoável após algum tempo, seu aplicativo seria interrompido por um período extremo de tempo (por exemplo, 30 segundos). Ele exibia um erro e continuava em seu caminho alegre ou tentava novamente.