Je veux attendre qu'une tâche <T> se termine avec quelques règles spéciales: si elle ne s'est pas terminée après X millisecondes, je veux afficher un message à l'utilisateur. Et si elle ne s'est pas terminée après Y millisecondes, je souhaite demander automatiquement l' annulation .
Je peux utiliser Task.ContinueWith pour attendre de manière asynchrone que la tâche se termine (c'est-à-dire planifier une action à exécuter lorsque la tâche est terminée), mais cela ne permet pas de spécifier un délai d'expiration. Je peux utiliser Task.Wait 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'expiration?
CancellationTokenSource
. Deux surcharges pour le constructeur sont disponibles, l'une prenant un délai milliseconde entier et l'autre prenant un retard TimeSpan.Réponses:
Que dis-tu de ça:
Et voici un excellent article de blog "Crafting a Task.TimeoutAfter Method" (de l'équipe MS Parallel Library) avec plus d'informations sur ce genre de choses .
Ajout : à la demande d'un commentaire sur ma réponse, voici une solution élargie qui inclut la gestion des annulations. Notez que le fait de passer l'annulation à la tâche et au minuteur signifie qu'il existe plusieurs façons d'annuler votre code, et vous devez vous assurer de tester et d'être sûr de les gérer correctement. Ne laissez pas au hasard diverses combinaisons et espérons que votre ordinateur fera ce qu'il faut lors de l'exécution.
la source
Task.Delay
tâche est soutenue par un temporisateur système qui continuera à être suivi jusqu'à l'expiration du délai d'expiration, quelle que soit la duréeSomeOperationAsync
. Donc, si cet extrait de code global s'exécute beaucoup dans une boucle serrée, vous consommez des ressources système pour les temporisateurs jusqu'à ce qu'ils expirent tous. La façon de résoudre ce problème serait d'avoir unCancellationToken
fichier que vous transmettez àTask.Delay(timeout, cancellationToken)
celui que vous annulez à laSomeOperationAsync
fin pour libérer la ressource du minuteur.Task
, toute exception stockée par la tâche est renvoyée à ce stade. Cela vous donne une chance de rattraperOperationCanceledException
(en cas d'annulation) ou toute autre exception (en cas de faute).Task.Wait(timeout)
bloquerait de manière synchrone au lieu d'attendre de manière asynchrone.Voici une version de méthode d'extension qui incorpore l'annulation du délai d'expiration lorsque la tâche d'origine se termine, comme suggéré par Andrew Arnott dans un commentaire à sa réponse .
la source
using
bloctask.Result
lorsqu'il est exécuté deux fois.task
) continuera-t-elle toujours à s'exécuter en cas d'expiration?TimeoutException
a un message par défaut approprié. Le remplacer par "L'opération a expiré." n'ajoute aucune valeur et provoque en fait une certaine confusion en impliquant qu'il y a une raison de la remplacer.Vous pouvez utiliser
Task.WaitAny
pour attendre la première de plusieurs tâches.Vous pouvez créer deux tâches supplémentaires (qui se terminent après les délais d'expiration spécifiés), puis utiliser
WaitAny
pour attendre la première des éventualités. Si la tâche qui s'est terminée en premier est votre tâche "de travail", alors vous avez terminé. Si la tâche qui s'est terminée en premier est une tâche d'expiration, vous pouvez alors réagir à l'expiration (par exemple, demander l'annulation).la source
below
.... c'est quoi? sur la base d'une commande SO?Et quelque chose comme ça?
Vous pouvez utiliser l'option Task.Wait sans bloquer le thread principal à l'aide d'une autre tâche.
la source
Voici un exemple entièrement travaillé basé sur la réponse la plus votée, qui est:
Le principal avantage de l'implémentation dans cette réponse est que des génériques ont été ajoutés, de sorte que la fonction (ou la tâche) peut renvoyer une valeur. Cela signifie que toute fonction existante peut être enveloppée dans une fonction de temporisation, par exemple:
Avant:
Après:
Ce code nécessite .NET 4.5.
Avertissements
Après avoir donné cette réponse, ce n'est généralement pas une bonne pratique d'avoir des exceptions levées dans votre code pendant le fonctionnement normal, sauf si vous devez absolument:
N'utilisez ce code que si vous ne pouvez absolument pas modifier la fonction que vous appelez, de sorte qu'elle expire après un moment précis
TimeSpan
.Cette réponse n'est vraiment applicable que lorsqu'il s'agit de bibliothèques de bibliothèques tierces que vous ne pouvez tout simplement pas refactoriser pour inclure un paramètre de délai d'expiration.
Comment écrire du code robuste
Si vous voulez écrire du code robuste, la règle générale est la suivante:
Si vous n'observez pas cette règle, votre code finira par frapper une opération qui échoue pour une raison quelconque, puis il se bloquera indéfiniment et votre application vient de se bloquer définitivement.
S'il y avait un délai raisonnable après un certain temps, votre application se bloquerait pendant une durée extrême (par exemple 30 secondes), puis elle afficherait une erreur et continuerait son chemin joyeux, ou réessayer.
la source
En utilisant l'excellente bibliothèque AsyncEx de Stephen Cleary , vous pouvez faire:
TaskCanceledException
sera lancé en cas de timeout.la source
Il s'agit d'une version légèrement améliorée des réponses précédentes.
CancellationToken
la tâche d'origine, et lorsque le délai d'expiration se produit, vous obtenez à laTimeoutException
place deOperationCanceledException
.Usage
InnerCallAsync
peut prendre beaucoup de temps.CallAsync
l'enveloppe avec un délai d'attente.la source
timeoutCancellation
dansdelayTask
. Actuellement, si vous déclenchez l'annulation,CancelAfterAsync
vous pouvez lancerTimeoutException
au lieu deTaskCanceledException
, car la causedelayTask
peut être terminée en premier.WhenAny
sont annulées par le même jeton,WhenAny
retournera la première tâche. Cette hypothèse était fausse. J'ai édité la réponse. Merci!Utilisez une minuterie pour gérer le message et l'annulation automatique. Une fois la tâche terminée, appelez Dispose sur les minuteries afin qu'elles ne se déclenchent jamais. Voici un exemple; changez taskDelay à 500, 1500 ou 2500 pour voir les différents cas:
En outre, le CTP Async fournit une méthode TaskEx.Delay qui encapsulera les temporisateurs dans les 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.
la source
task.Wait()
.Une autre façon de résoudre ce problème consiste à utiliser les extensions réactives:
Testez ci-dessus en utilisant le code ci-dessous dans votre test unitaire, cela fonctionne pour moi
Vous pourriez avoir besoin de l'espace de noms suivant:
la source
Une version générique de la réponse de @ Kevan ci-dessus, utilisant des extensions réactives.
Avec Scheduler en option:
BTW: Lorsqu'un Timeout se produit, une exception de timeout sera levée
la source
Si vous utilisez un BlockingCollection pour planifier la tâche, le producteur peut exécuter la tâche potentiellement longue et le consommateur peut utiliser la méthode TryTake qui a un jeton d'expiration et d'annulation intégré.
la source
J'ai ressenti la
Task.Delay()
tâche etCancellationTokenSource
dans les autres réponses un peu pour mon cas d'utilisation dans une boucle de réseau serrée.Et bien que la méthode Crafting a Task.TimeoutAfter de Joe Hoag sur les blogs MSDN ait été inspirante, j'étais un peu fatigué d'utiliser
TimeoutException
pour le contrôle de flux pour la même raison que ci-dessus, car des délais d'attente sont attendus plus souvent qu'autrement.Je suis donc allé avec cela, qui gère également les optimisations mentionnées dans le blog:
Un exemple de cas d'utilisation est en tant que tel:
la source
Quelques variantes de la réponse d'Andrew Arnott:
Si vous souhaitez attendre une tâche existante et savoir si elle s'est terminée ou a expiré, mais ne souhaitez pas l'annuler si le délai d'expiration se produit:
Si vous souhaitez démarrer une tâche de travail et annuler le travail si le délai d'expiration se produit:
Si vous avez déjà créé une tâche que vous souhaitez annuler en cas d'expiration:
Autre commentaire, ces versions annuleront le temporisateur si le délai d'expiration ne se produit pas, donc plusieurs appels n'entraîneront pas le cumul des temporisateurs.
sjb
la source
Je recompose les idées d'autres réponses ici et cette réponse sur un autre fil dans une méthode d'extension de style Try. Cela présente un avantage si vous souhaitez une méthode d'extension, tout en évitant une exception lors de la temporisation.
la source
Certainement pas, mais c'est une option si ... Je ne peux pas penser à une raison valable.
la source