Hier ist ein vollständig ausgearbeitetes Beispiel, das auf der am häufigsten gewählten Antwort basiert, die lautet:
int timeout = 1000;
var task = SomeOperationAsync();
if (await Task.WhenAny(task, Task.Delay(timeout)) == task) {
// task completed within timeout
} else {
// timeout logic
}
Der Hauptvorteil der Implementierung in dieser Antwort ist, dass Generika hinzugefügt wurden, so dass die Funktion (oder Aufgabe) einen Wert zurückgeben kann. Dies bedeutet, dass jede bestehende Funktion in eine Timeout-Funktion verpackt werden kann, z. B.:
Vorher:
int x = MyFunc();
Danach:
// Throws a TimeoutException if MyFunc takes more than 1 second
int x = TimeoutAfter(MyFunc, TimeSpan.FromSeconds(1));
Dieser Code erfordert .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();
}
}
}
}
Vorbehalte
Nachdem diese Antwort gegeben wurde, ist es im Allgemeinen no Es ist keine gute Praxis, während des normalen Betriebs Ausnahmen in Ihrem Code auszulösen, es sei denn, Sie müssen dies unbedingt tun:
- Jedes Mal, wenn eine Ausnahme ausgelöst wird, ist dies ein extrem schwerer Vorgang,
- Ausnahmen können Ihren Code um den Faktor 100 oder mehr verlangsamen, wenn sich die Ausnahmen in einer engen Schleife befinden.
Verwenden Sie diesen Code nur, wenn Sie die Funktion, die Sie aufrufen, absolut nicht ändern können, damit sie nach einer bestimmten TimeSpan
.
Diese Antwort ist wirklich nur anwendbar, wenn Sie mit Bibliotheken von Drittanbietern zu tun haben, die Sie einfach nicht so umgestalten können, dass sie einen Timeout-Parameter enthalten.
Wie man robusten Code schreibt
Wenn Sie einen robusten Code schreiben wollen, gilt die folgende Regel:
Jede einzelne Operation, die potenziell unbegrenzt blockiert werden kann, muss eine Zeitüberschreitung haben.
もし、あなたが nicht Wenn Sie diese Regel beachten, wird Ihr Code irgendwann auf einen Vorgang stoßen, der aus irgendeinem Grund fehlschlägt, dann wird er auf unbestimmte Zeit blockiert, und Ihre Anwendung ist einfach dauerhaft hängen geblieben.
Wenn es nach einer gewissen Zeit eine vernünftige Zeitüberschreitung gäbe, würde Ihre Anwendung für eine extreme Zeitspanne (z. B. 30 Sekunden) hängen bleiben und dann entweder eine Fehlermeldung anzeigen und ihren fröhlichen Weg fortsetzen, oder es erneut versuchen.