553 votes

Attendre de manière asynchrone que la tâche<T> se termine avec un délai d'attente.

Je veux attendre un Tâche<T> pour se terminer avec certaines règles spéciales : S'il n'est pas terminé après X millisecondes, je veux afficher un message à l'utilisateur. Et s'il n'est pas terminé après Y millisecondes, je veux automatiquement demande d'annulation .

Je peux utiliser Task.ContinueWith pour attendre de manière asynchrone la fin de la tâche (c'est-à-dire programmer une action qui sera exécutée lorsque la tâche sera terminée), mais cela ne permet pas de spécifier un délai d'attente. Je peux utiliser Tâche.attendre pour attendre de manière synchrone que la tâche se termine avec un délai d'attente, mais cela bloque mon thread. Comment puis-je attendre de manière asynchrone que la tâche se termine avec un délai d'attente ?

764voto

Andrew Arnott Points 35346

Que dites-vous de ça ?

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

Et voici un excellent article de blog "Crafting a Task.TimeoutAfter Method" (de l'équipe de la bibliothèque parallèle de MS) avec plus d'informations sur ce genre de choses. .

Addition : à la demande d'un commentaire sur ma réponse, voici une solution étendue qui inclut la gestion de l'annulation. Notez que le fait de passer l'annulation à la tâche et à la minuterie signifie qu'il y a de multiples façons dont l'annulation peut être vécue dans votre code, et vous devez être sûr de tester et d'être sûr que vous gérez correctement toutes ces façons. Ne laissez pas au hasard diverses combinaisons et espérez que votre ordinateur fasse le bon choix au moment de l'exécution.

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
}

332voto

Lawrence Johnston Points 9723

Voici une version de la méthode d'extension qui incorpore l'annulation du délai d'attente lorsque la tâche originale est terminée, comme l'a suggéré Andrew Arnott dans un commentaire sur le site Web de la Commission européenne. sa réponse .

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

    var timeoutCancellationTokenSource = new CancellationTokenSource();

    var completedTask = await Task.WhenAny(task, Task.Delay(timeout, timeoutCancellationTokenSource.Token));
    if (completedTask == task) {
        timeoutCancellationTokenSource.Cancel();
        return await task;
    } else {
        throw new TimeoutException("The operation has timed out.");
    }
}

51voto

Tomas Petricek Points 118959

Vous pouvez utiliser Task.WaitAny pour attendre la première de plusieurs tâches.

Vous pourriez créer deux tâches supplémentaires (qui se terminent après les délais spécifiés) et ensuite utiliser la fonction WaitAny d'attendre celle qui se termine en premier. Si la tâche qui s'est achevée en premier est votre tâche de "travail", alors vous n'avez rien à faire. Si la tâche qui s'est terminée en premier est une tâche à délai d'attente, alors vous pouvez réagir au délai d'attente (par exemple, demander l'annulation).

17voto

as-cii Points 7028

Et quelque chose comme ça ?

    const int x = 3000;
    const int y = 1000;

    static void Main(string[] args)
    {
        // Your scheduler
        TaskScheduler scheduler = TaskScheduler.Default;

        Task nonblockingTask = new Task(() =>
            {
                CancellationTokenSource source = new CancellationTokenSource();

                Task t1 = new Task(() =>
                    {
                        while (true)
                        {
                            // Do something
                            if (source.IsCancellationRequested)
                                break;
                        }
                    }, source.Token);

                t1.Start(scheduler);

                // Wait for task 1
                bool firstTimeout = t1.Wait(x);

                if (!firstTimeout)
                {
                    // If it hasn't finished at first timeout display message
                    Console.WriteLine("Message to user: the operation hasn't completed yet.");

                    bool secondTimeout = t1.Wait(y);

                    if (!secondTimeout)
                    {
                        source.Cancel();
                        Console.WriteLine("Operation stopped!");
                    }
                }
            });

        nonblockingTask.Start();
        Console.WriteLine("Do whatever you want...");
        Console.ReadLine();
    }

Vous pouvez utiliser l'option Task.Wait sans bloquer le thread principal en utilisant une autre Task.

9voto

Quartermeister Points 24729

Utilisez un Minuterie pour traiter le message et l'annulation automatique. Lorsque la tâche est terminée, appelez Dispose sur les temporisateurs afin qu'ils ne se déclenchent jamais. Voici un exemple ; changez taskDelay à 500, 1500, ou 2500 pour voir les différents cas :

using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
    class Program
    {
        private static Task CreateTaskWithTimeout(
            int xDelay, int yDelay, int taskDelay)
        {
            var cts = new CancellationTokenSource();
            var token = cts.Token;
            var task = Task.Factory.StartNew(() =>
            {
                // Do some work, but fail if cancellation was requested
                token.WaitHandle.WaitOne(taskDelay);
                token.ThrowIfCancellationRequested();
                Console.WriteLine("Task complete");
            });
            var messageTimer = new Timer(state =>
            {
                // Display message at first timeout
                Console.WriteLine("X milliseconds elapsed");
            }, null, xDelay, -1);
            var cancelTimer = new Timer(state =>
            {
                // Display message and cancel task at second timeout
                Console.WriteLine("Y milliseconds elapsed");
                cts.Cancel();
            }
                , null, yDelay, -1);
            task.ContinueWith(t =>
            {
                // Dispose the timers when the task completes
                // This will prevent the message from being displayed
                // if the task completes before the timeout
                messageTimer.Dispose();
                cancelTimer.Dispose();
            });
            return task;
        }

        static void Main(string[] args)
        {
            var task = CreateTaskWithTimeout(1000, 2000, 2500);
            // The task has been started and will display a message after
            // one timeout and then cancel itself after the second
            // You can add continuations to the task
            // or wait for the result as needed
            try
            {
                task.Wait();
                Console.WriteLine("Done waiting for task");
            }
            catch (AggregateException ex)
            {
                Console.WriteLine("Error waiting for task:");
                foreach (var e in ex.InnerExceptions)
                {
                    Console.WriteLine(e);
                }
            }
        }
    }
}

En outre, le CTP asynchrone fournit une méthode TaskEx.Delay qui englobe les temporisateurs dans des tâches pour vous. Cela peut vous donner plus de contrôle pour faire des choses comme définir le TaskScheduler pour la continuation lorsque le timer se déclenche.

private static Task CreateTaskWithTimeout(
    int xDelay, int yDelay, int taskDelay)
{
    var cts = new CancellationTokenSource();
    var token = cts.Token;
    var task = Task.Factory.StartNew(() =>
    {
        // Do some work, but fail if cancellation was requested
        token.WaitHandle.WaitOne(taskDelay);
        token.ThrowIfCancellationRequested();
        Console.WriteLine("Task complete");
    });

    var timerCts = new CancellationTokenSource();

    var messageTask = TaskEx.Delay(xDelay, timerCts.Token);
    messageTask.ContinueWith(t =>
    {
        // Display message at first timeout
        Console.WriteLine("X milliseconds elapsed");
    }, TaskContinuationOptions.OnlyOnRanToCompletion);

    var cancelTask = TaskEx.Delay(yDelay, timerCts.Token);
    cancelTask.ContinueWith(t =>
    {
        // Display message and cancel task at second timeout
        Console.WriteLine("Y milliseconds elapsed");
        cts.Cancel();
    }, TaskContinuationOptions.OnlyOnRanToCompletion);

    task.ContinueWith(t =>
    {
        timerCts.Cancel();
    });

    return task;
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X