548 Stimmen

Asynchrones Warten auf die Fertigstellung von Task<T> mit Timeout

Ich möchte auf eine Aufgabe<T> mit einigen Sonderregeln zu ergänzen: Wenn der Vorgang nach X Millisekunden noch nicht abgeschlossen ist, möchte ich dem Benutzer eine Meldung anzeigen. Und wenn er nach Y Millisekunden noch nicht abgeschlossen ist, möchte ich automatisch Stornierung beantragen .

Ich kann Task.ContinueWith um asynchron auf den Abschluss der Aufgabe zu warten (d.h. eine Aktion zu planen, die ausgeführt wird, wenn die Aufgabe abgeschlossen ist), aber das erlaubt es nicht, einen Timeout anzugeben. Ich kann verwenden Aufgabe.Warten synchron zu warten, bis die Aufgabe mit einem Timeout abgeschlossen ist, aber das blockiert meinen Thread. Wie kann ich asynchron warten, bis die Aufgabe mit einer Zeitüberschreitung abgeschlossen ist?

759voto

Andrew Arnott Punkte 77359

Wie wäre es damit:

int timeout = 1000;
var task = SomeOperationAsync();
if (await Task.WhenAny(task, Task.Delay(timeout)) == task) {
    // task completed within timeout
} else { 
    // timeout logic
}

Und hier ist ein großartiger Blogbeitrag "Crafting a Task.TimeoutAfter Method" (vom MS Parallel Library Team) mit weiteren Informationen zu dieser Art von Dingen .

Zusatz Auf Wunsch eines Kommentars zu meiner Antwort ist hier eine erweiterte Lösung, die auch die Behandlung von Stornierungen beinhaltet. Beachten Sie, dass die Übergabe des Abbruchs an die Aufgabe und den Timer bedeutet, dass es mehrere Möglichkeiten gibt, wie der Abbruch in Ihrem Code erlebt werden kann, und Sie sollten sicher sein, dass Sie auf alle diese Möglichkeiten testen und sie richtig behandeln. Überlassen Sie die verschiedenen Kombinationen nicht dem Zufall und hoffen Sie, dass Ihr Computer zur Laufzeit das Richtige tut.

int timeout = 1000;
var task = SomeOperationAsync(cancellationToken);
if (await Task.WhenAny(task, Task.Delay(timeout, cancellationToken)) == task)
{
    // Task completed within timeout.
    // Consider that the task may have faulted or been canceled.
    // We re-await the task so that any exceptions/cancellation is rethrown.
    await task;

}
else
{
    // timeout/cancellation logic
}

330voto

Lawrence Johnston Punkte 62528

Hier ist eine Version der Erweiterungsmethode, die den Abbruch der Zeitüberschreitung einschließt, wenn die ursprüngliche Aufgabe abgeschlossen ist, wie von Andrew Arnott in einem Kommentar zu seine Antwort .

public static async Task<TResult> TimeoutAfter<TResult>(this Task<TResult> task, TimeSpan timeout) {

    using (var timeoutCancellationTokenSource = new CancellationTokenSource()) {

        var completedTask = await Task.WhenAny(task, Task.Delay(timeout, timeoutCancellationTokenSource.Token));
        if (completedTask == task) {
            timeoutCancellationTokenSource.Cancel();
            return await task;  // Very important in order to propagate exceptions
        } else {
            throw new TimeoutException("The operation has timed out.");
        }
    }
}

107voto

Vijay Nirmal Punkte 3936

Ab .Net 6 (Preview 7) oder später gibt es eine neue Build-in-Methode Aufgabe.WaitAsync um dies zu erreichen.

// Using TimeSpan
await myTask.WaitAsync(TimeSpan.FromSeconds(10));

// Using CancellationToken
await myTask.WaitAsync(cancellationToken);

// Using both TimeSpan and CancellationToken
await myTask.WaitAsync(TimeSpan.FromSeconds(10), cancellationToken);

51voto

Tomas Petricek Punkte 233658

Sie können verwenden Task.WaitAny um die erste von mehreren Aufgaben zu warten.

Sie könnten zwei zusätzliche Aufgaben erstellen (die nach den angegebenen Zeitüberschreitungen abgeschlossen werden) und dann mit WaitAny um zu warten, was zuerst abgeschlossen wird. Wenn die zuerst beendete Aufgabe Ihre "Arbeits"-Aufgabe ist, dann sind Sie fertig. Wenn die zuerst beendete Aufgabe eine Zeitüberschreitungsaufgabe ist, können Sie auf die Zeitüberschreitung reagieren (z. B. einen Abbruch anfordern).

30voto

Josef Bláha Punkte 837

Dies ist eine leicht verbesserte Version der früheren Antworten.

  • Zusätzlich zu Die Antwort von Lawrence wird die ursprüngliche Aufgabe abgebrochen, wenn eine Zeitüberschreitung eintritt.
  • Zusätzlich zu den sjb's Antwortvarianten 2 und 3 können Sie bereitstellen CancellationToken für die ursprüngliche Aufgabe, und wenn eine Zeitüberschreitung auftritt, erhalten Sie TimeoutException 代わりに OperationCanceledException .

    async Task<TResult> CancelAfterAsync<TResult>( Func<CancellationToken, Task<TResult>> startTask, TimeSpan timeout, CancellationToken cancellationToken) { using (var timeoutCancellation = new CancellationTokenSource()) using (var combinedCancellation = CancellationTokenSource .CreateLinkedTokenSource(cancellationToken, timeoutCancellation.Token)) { var originalTask = startTask(combinedCancellation.Token); var delayTask = Task.Delay(timeout, timeoutCancellation.Token); var completedTask = await Task.WhenAny(originalTask, delayTask); // Cancel timeout to stop either task: // - Either the original task completed, so we need to cancel the delay task. // - Or the timeout expired, so we need to cancel the original task. // Canceling will not affect a task, that is already completed. timeoutCancellation.Cancel(); if (completedTask == originalTask) { // original task completed return await originalTask; } else { // timeout throw new TimeoutException(); } } }


Verwendung

InnerCallAsync kann lange Zeit in Anspruch nehmen. CallAsync verpackt es mit einer Auszeit.

async Task<int> CallAsync(CancellationToken cancellationToken)
{
    var timeout = TimeSpan.FromMinutes(1);
    int result = await CancelAfterAsync(ct => InnerCallAsync(ct), timeout,
        cancellationToken);
    return result;
}

async Task<int> InnerCallAsync(CancellationToken cancellationToken)
{
    return 42;
}

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X