Voici un exemple entièrement travaillé basé sur la réponse la plus votée, qui est:
int timeout = 1000;
var task = SomeOperationAsync();
if (await Task.WhenAny(task, Task.Delay(timeout)) == task) {
// task completed within timeout
} else {
// timeout logic
}
Le principal avantage de l'implémentation dans cette réponse est que des génériques ont été ajoutés, de sorte que la fonction (ou la tâche) peut renvoyer une valeur. Cela signifie que toute fonction existante peut être enveloppée dans une fonction de temporisation, par exemple:
Avant:
int x = MyFunc();
Après:
// Throws a TimeoutException if MyFunc takes more than 1 second
int x = TimeoutAfter(MyFunc, TimeSpan.FromSeconds(1));
Ce code nécessite .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();
}
}
}
}
Avertissements
Après avoir donné cette réponse, ce n'est généralement pas une bonne pratique d'avoir des exceptions levées dans votre code pendant le fonctionnement normal, sauf si vous devez absolument:
- Chaque fois qu'une exception est levée, c'est une opération extrêmement lourde,
- Les exceptions peuvent ralentir votre code d'un facteur 100 ou plus si les exceptions sont dans une boucle étroite.
N'utilisez ce code que si vous ne pouvez absolument pas modifier la fonction que vous appelez, de sorte qu'elle expire après un moment précis TimeSpan
.
Cette réponse n'est vraiment applicable que lorsqu'il s'agit de bibliothèques de bibliothèques tierces que vous ne pouvez tout simplement pas refactoriser pour inclure un paramètre de délai d'expiration.
Comment écrire du code robuste
Si vous voulez écrire du code robuste, la règle générale est la suivante:
Chaque opération qui pourrait potentiellement se bloquer indéfiniment doit avoir un délai d'expiration.
Si vous n'observez pas cette règle, votre code finira par frapper une opération qui échoue pour une raison quelconque, puis il se bloquera indéfiniment et votre application vient de se bloquer définitivement.
S'il y avait un délai raisonnable après un certain temps, votre application se bloquerait pendant une durée extrême (par exemple 30 secondes), puis elle afficherait une erreur et continuerait son chemin joyeux, ou réessayer.