Hier ist ein vollständig ausgearbeitetes Beispiel, das auf der am besten bewerteten Antwort basiert:
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 besteht darin, dass Generika hinzugefügt wurden, sodass die Funktion (oder Aufgabe) einen Wert zurückgeben kann. Dies bedeutet, dass jede vorhandene Funktion in eine Timeout-Funktion eingeschlossen werden kann, z.
Vor:
int x = MyFunc();
Nach:
// 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();
}
}
}
}
Vorsichtsmaßnahmen
Nachdem Sie diese Antwort gegeben haben, ist es im Allgemeinen keine gute Praxis, während des normalen Betriebs Ausnahmen in Ihren Code zu werfen, es sei denn, Sie müssen unbedingt:
- Jedes Mal, wenn eine Ausnahme ausgelöst wird, ist dies eine extrem schwere Operation.
- 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 aufgerufene Funktion absolut nicht ändern können, sodass nach einer bestimmten Zeit eine Zeitüberschreitung auftritt TimeSpan
.
Diese Antwort gilt nur für Bibliotheksbibliotheken von Drittanbietern, die Sie einfach nicht umgestalten können, um einen Timeout-Parameter einzuschließen.
Wie schreibe ich robusten Code
Wenn Sie robusten Code schreiben möchten, lautet die allgemeine Regel:
Jede einzelne Operation, die möglicherweise auf unbestimmte Zeit blockiert werden kann, muss eine Zeitüberschreitung aufweisen.
Wenn Sie dies nicht tun diese Regel einhalten, wird Ihr Code möglicherweise einen Vorgang ausführen, der aus irgendeinem Grund fehlschlägt. Dann wird er auf unbestimmte Zeit blockiert und Ihre App ist gerade dauerhaft hängen geblieben.
Wenn nach einiger Zeit eine angemessene Zeitüberschreitung auftritt, bleibt Ihre App extrem lange hängen (z. B. 30 Sekunden). Dann wird entweder ein Fehler angezeigt und der fröhliche Weg fortgesetzt oder es wird erneut versucht.