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?

7voto

Kevan Punkte 109

Eine andere Möglichkeit, dieses Problem zu lösen, ist die Verwendung von Reactive Extensions:

public static Task TimeoutAfter(this Task task, TimeSpan timeout, IScheduler scheduler)
{
        return task.ToObservable().Timeout(timeout, scheduler).ToTask();
}

Testen Sie oben mit untenstehendem Code in Ihrem Unit-Test, es funktioniert bei mir

TestScheduler scheduler = new TestScheduler();
Task task = Task.Run(() =>
                {
                    int i = 0;
                    while (i < 5)
                    {
                        Console.WriteLine(i);
                        i++;
                        Thread.Sleep(1000);
                    }
                })
                .TimeoutAfter(TimeSpan.FromSeconds(5), scheduler)
                .ContinueWith(t => { }, TaskContinuationOptions.OnlyOnFaulted);

scheduler.AdvanceBy(TimeSpan.FromSeconds(6).Ticks);

Möglicherweise benötigen Sie den folgenden Namespace:

using System.Threading.Tasks;
using System.Reactive.Subjects;
using System.Reactive.Linq;
using System.Reactive.Threading.Tasks;
using Microsoft.Reactive.Testing;
using System.Threading;
using System.Reactive.Concurrency;

5voto

flodis Punkte 915

Aus Spaß an der Freude habe ich eine 'OnTimeout'-Erweiterung für Task erstellt. Bei Zeitüberschreitung führt Task die gewünschte Inline-Lambda-Aktion() aus und gibt true zurück, ansonsten false.

public static async Task<bool> OnTimeout<T>(this T t, Action<T> action, int waitms) where T : Task
{
    if (!(await Task.WhenAny(t, Task.Delay(waitms)) == t))
    {
        action(t);
        return true;
    } else {
        return false;
    }
}

Die OnTimeout-Erweiterung gibt ein boolsches Ergebnis zurück, das einer Variablen zugewiesen werden kann, wie in diesem Beispiel, das einen UDP-Socket Async aufruft:

var t = UdpSocket.ReceiveAsync();

var timeout = await t.OnTimeout(task => {
    Console.WriteLine("No Response");
}, 5000);

Die Variable "task" ist im Timeout-Lambda für die weitere Verarbeitung zugänglich.

Die Verwendung von Action, die ein Objekt aufnimmt, kann zu verschiedenen anderen Verlängerungsdesigns inspirieren.

5voto

1iveowl Punkte 1512

Eine allgemeine Version der obigen Antwort von @Kevan, unter Verwendung von Reactive Extensions.

public static Task<T> TimeoutAfter<T>(this Task<T> task, TimeSpan timeout, IScheduler scheduler)
{
    return task.ToObservable().Timeout(timeout, scheduler).ToTask();
}

Mit optionalem Scheduler:

public static Task<T> TimeoutAfter<T>(this Task<T> task, TimeSpan timeout, Scheduler scheduler = null)
{
    return scheduler is null 
       ? task.ToObservable().Timeout(timeout).ToTask() 
       : task.ToObservable().Timeout(timeout, scheduler).ToTask();
}

BTW: Wenn eine Zeitüberschreitung auftritt, wird eine Timeout-Ausnahme ausgelöst

3voto

Edward Brey Punkte 37840

Erstellen Sie eine Erweiterung, um auf den Abschluss der Aufgabe oder eine Verzögerung zu warten, je nachdem, was zuerst eintritt. Werfen Sie eine Ausnahme, wenn die Verzögerung gewinnt.

public static async Task<TResult> WithTimeout<TResult>(this Task<TResult> task, TimeSpan timeout)
{
    if (await Task.WhenAny(task, Task.Delay(timeout)) != task)
        throw new TimeoutException();
    return await task;
}

2voto

thomasrea0113 Punkte 206

Das ist also uralt, aber es gibt eine viel bessere moderne Lösung. Nicht sicher, welche Version von c#/.NET erforderlich ist, aber dies ist, wie ich es tun:

... Other method code not relevant to the question.

// a token source that will timeout at the specified interval, or if cancelled outside of this scope
using var timeoutTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(5));
using var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(token, timeoutTokenSource.Token);

async Task<MessageResource> FetchAsync()
{
    try
    {
        return await MessageResource.FetchAsync(m.Sid);
    } catch (TaskCanceledException e)
    {
        if (timeoutTokenSource.IsCancellationRequested)
            throw new TimeoutException("Timeout", e);
        throw;
    }
}

return await Task.Run(FetchAsync, linkedTokenSource.Token);

その CancellationTokenSource Konstruktor nimmt eine TimeSpan der dazu führt, dass das Token nach Ablauf dieses Intervalls abgebrochen wird. Sie können dann Ihren asynchronen (oder synchronen) Code in einen weiteren Aufruf von Task.Run und übergibt das Timeout-Token.

Dies setzt voraus, dass Sie ein Stornierungs-Token übergeben (die token Variable). Wenn Sie die Aufgabe nicht getrennt von der Zeitüberschreitung abbrechen müssen, können Sie einfach timeoutTokenSource direkt. Andernfalls erstellen Sie linkedTokenSource , die bei einer Zeitüberschreitung abgebrochen wird, ou wenn sie sonst annulliert wird.

Wir fangen dann einfach OperationCancelledException und prüfen, welches Token die Ausnahme ausgelöst hat, und eine TimeoutException wenn eine Zeitüberschreitung dies ausgelöst hat. Andernfalls lösen wir einen Re-Throw aus.

Außerdem verwende ich hier lokale Funktionen, die in C# 7 eingeführt wurden, aber Sie könnten problemlos Lambda- oder tatsächliche Funktionen verwenden, um denselben Effekt zu erzielen. In ähnlicher Weise führte c# 8 eine einfachere Syntax für using-Anweisungen, aber diese sind einfach genug, um neu zu schreiben.

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