Ecco un esempio completo basato sulla risposta più votata, che è:
int timeout = 1000;
var task = SomeOperationAsync();
if (await Task.WhenAny(task, Task.Delay(timeout)) == task) {
// task completed within timeout
} else {
// timeout logic
}
Il vantaggio principale dell'implementazione in questa risposta è che i generici sono stati aggiunti, quindi la funzione (o attività) può restituire un valore. Ciò significa che qualsiasi funzione esistente può essere racchiusa in una funzione di timeout, ad esempio:
Prima:
int x = MyFunc();
Dopo:
// Throws a TimeoutException if MyFunc takes more than 1 second
int x = TimeoutAfter(MyFunc, TimeSpan.FromSeconds(1));
Questo codice richiede .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();
}
}
}
}
Avvertenze
Dopo aver dato questa risposta, in genere non è una buona prassi avere eccezioni nel codice durante il normale funzionamento, a meno che non sia necessario:
- Ogni volta che viene generata un'eccezione, è un'operazione estremamente pesante,
- Le eccezioni possono rallentare il codice di un fattore pari o superiore a 100 se le eccezioni sono in un ciclo stretto.
Usa questo codice solo se non puoi assolutamente modificare la funzione che stai chiamando in modo che scada dopo uno specifico TimeSpan
.
Questa risposta è davvero applicabile solo quando si ha a che fare con librerie di librerie di terze parti che semplicemente non è possibile includere in un parametro di timeout.
Come scrivere codice robusto
Se si desidera scrivere un codice affidabile, la regola generale è questa:
Ogni singola operazione che potrebbe potenzialmente bloccarsi indefinitamente, deve avere un timeout.
Se non lo fai questa regola, il tuo codice finirà per eseguire un'operazione che non riesce per qualche motivo, quindi si bloccherà indefinitamente e la tua app si è bloccata in modo permanente.
Se dopo un certo periodo di tempo si verificasse un timeout ragionevole, la tua app si bloccherebbe per un periodo di tempo estremo (ad es. 30 secondi), quindi visualizzerebbe un errore e continuerà nel modo giusto o riprova.