Quand utiliser Task.Delay, quand utiliser Thread.Sleep?

386

Existe-t-il de bonnes règles pour savoir quand utiliser Task.Delay contre Thread.Sleep ?

  • Plus précisément, existe-t-il une valeur minimale à prévoir pour que l'un soit efficace / efficient par rapport à l'autre?
  • Enfin, étant donné que Task.Delay provoque un changement de contexte sur une machine d'état asynchrone / attente, y a-t-il un surcoût lié à son utilisation?
Tom K.
la source
2
10 ms, c'est beaucoup de cycles dans le monde informatique ...
Brad Christie
À quelle vitesse devrait-il être? Quels problèmes de performances rencontrez-vous?
LB
4
Je pense que la question la plus pertinente est dans quel contexte avez-vous l'intention d'utiliser l'un ou l'autre de ces éléments? Sans ces informations, la portée est trop large. Qu'entendez-vous par efficace / efficient? Parlez-vous de précision, d'efficacité énergétique, etc.? Je suis très curieux de savoir dans quel contexte cela compte.
James World
4
Le minimum est de 15,625 ms, les valeurs inférieures au taux d'interruption d'horloge n'ont aucun effet. Task.Delay brûle toujours un System.Threading.Timer, Sleep n'a pas de surcharge. Vous ne vous inquiétez pas des frais généraux lorsque vous écrivez du code qui ne fait rien.
Hans Passant
quelque chose que je n'ai pas vu mentionné, mais je pense que c'est important, serait que Task.Delay prend en charge un CancellationToken, ce qui signifie que vous pouvez interrompre le retard, si vous, par exemple, l'utilisez pour ralentir un processus de cycle. cela signifie également que votre processus peut réagir rapidement lorsque vous souhaitez l'annuler. mais vous pouvez obtenir la même chose avec Thread.Sleep en raccourcissant l'intervalle du cycle de sommeil et vérifier le jeton manuel.
Droa

Réponses:

370

À utiliser Thread.Sleeplorsque vous souhaitez bloquer le thread actuel.

À utiliser Task.Delaylorsque vous souhaitez un délai logique sans bloquer le thread actuel.

L'efficacité ne doit pas être une préoccupation primordiale avec ces méthodes. Leur utilisation principale dans le monde réel est la temporisation des nouvelles tentatives pour les opérations d'E / S, qui sont de l'ordre des secondes plutôt que des millisecondes.

Stephen Cleary
la source
3
C'est le même cas d'utilisation principal: un minuteur de nouvelle tentative.
Stephen Cleary
4
Ou lorsque vous ne voulez pas mâcher le CPU dans une boucle principale.
Eddie Parker
5
@RoyiNamir: Non. Il n'y a pas d '"autre thread". En interne, il est implémenté avec une minuterie.
Stephen Cleary
20
La suggestion de ne pas se soucier de l'efficacité est mal avisée. Thread.Sleepva bloquer le thread actuel qui provoque un changement de contexte. Si vous utilisez un pool de threads, cela peut également entraîner l'allocation d'un nouveau thread. Les deux opérations sont assez lourdes alors que le multitâche coopératif fourni par Task.Delayetc. est conçu pour éviter tous ces frais généraux, maximiser le débit, permettre l'annulation et fournir un code plus propre.
Corillian
2
@LucaCremry onesi: I would use Thread.Sleep` pour attendre à l'intérieur d'une méthode synchrone. Cependant, je ne fais jamais cela dans le code de production; d'après mon expérience, tout ce Thread.Sleepque j'ai jamais vu est révélateur d'une sorte de problème de conception qui doit être corrigé correctement.
Stephen Cleary
243

La plus grande différence entre Task.Delayet Thread.Sleepest qu'il Task.Delayest destiné à s'exécuter de manière asynchrone. Cela n'a aucun sens d'utiliser Task.Delaydu code synchrone. C'est une TRÈS mauvaise idée à utiliser Thread.Sleepen code asynchrone.

Normalement, vous appelez Task.Delay() avec le awaitmot - clé:

await Task.Delay(5000);

ou, si vous souhaitez exécuter du code avant le délai:

var sw = new Stopwatch();
sw.Start();
Task delay = Task.Delay(5000);
Console.WriteLine("async: Running for {0} seconds", sw.Elapsed.TotalSeconds);
await delay;

Devinez ce que cela va imprimer? Fonctionne pendant 0,0070048 secondes. Si nous déplaçons ce qui await delayprécède à la Console.WriteLineplace, il imprimera Running pendant 5.0020168 secondes.

Regardons la différence avec Thread.Sleep:

class Program
{
    static void Main(string[] args)
    {
        Task delay = asyncTask();
        syncCode();
        delay.Wait();
        Console.ReadLine();
    }

    static async Task asyncTask()
    {
        var sw = new Stopwatch();
        sw.Start();
        Console.WriteLine("async: Starting");
        Task delay = Task.Delay(5000);
        Console.WriteLine("async: Running for {0} seconds", sw.Elapsed.TotalSeconds);
        await delay;
        Console.WriteLine("async: Running for {0} seconds", sw.Elapsed.TotalSeconds);
        Console.WriteLine("async: Done");
    }

    static void syncCode()
    {
        var sw = new Stopwatch();
        sw.Start();
        Console.WriteLine("sync: Starting");
        Thread.Sleep(5000);
        Console.WriteLine("sync: Running for {0} seconds", sw.Elapsed.TotalSeconds);
        Console.WriteLine("sync: Done");
    }
}

Essayez de prédire ce que cela va imprimer ...

async: Démarrage
async: Fonctionnement pendant 0.0070048 secondes de
synchronisation: Démarrage
async: Fonctionnement pendant 5.0119008 secondes
async: Terminé
sync: Fonctionnement pendant 5.0020168 secondes
sync: Terminé

En outre, il est intéressant de noter que Thread.Sleepc'est beaucoup plus précis, la précision ms n'est pas vraiment un problème, alors qu'elle Task.Delaypeut prendre 15-30 ms minimum. Les frais généraux sur les deux fonctions sont minimes par rapport à la précision en ms qu'ils ont (utilisez StopwatchClass si vous avez besoin de quelque chose de plus précis). Thread.Sleepattache toujours votre fil, Task.Delayrelâchez-le pour effectuer d'autres travaux pendant que vous attendez.

Dorus
la source
15
Pourquoi est-ce "une TRÈS mauvaise idée d'utiliser Thread.Sleep en code asynchrone"?
sunside
69
@sunside L'un des principaux avantages du code asynchrone est de permettre à un thread de travailler sur plusieurs tâches à la fois, en évitant de bloquer les appels. Cela évite le besoin d'énormes quantités de threads individuels et permet à un pool de threads de traiter de nombreuses demandes à la fois. Cependant, étant donné que le code asynchrone s'exécute généralement sur le pool de threads, bloquer inutilement un seul thread avec Thread.Sleep()consomme un thread entier qui pourrait autrement être utilisé ailleurs. Si de nombreuses tâches sont exécutées avec Thread.Sleep (), il y a de fortes chances d'épuiser tous les threads de pool de threads et d'entraver sérieusement les performances.
Ryan
1
Tu piges. Il me manquait la notion de code asynchrone dans le sens des asyncméthodes car elles sont encouragées à être utilisées. C'est fondamentalement juste une mauvaise idée de s'exécuter Thread.Sleep()dans un thread threadpool, pas une mauvaise idée en général. Après tout, il y a TaskCreationOptions.LongRunningquand vous empruntez la Task.Factory.StartNew()route (bien que découragée) .
Sunside
6
bravo pourawait wait
Eric Wu
2
@Reyhn La documentation à ce sujet Tasl.Delayutilise le temporisateur système. Etant donné que "l'horloge système" coche "à un taux constant.", La vitesse de la coche du minuteur système est d'environ 16 ms, tout retard que vous demandez sera arrondi à un certain nombre de tiques de l'horloge système, compensé par le temps jusqu'au premier cocher. Consultez la documentation msdn sur Task.Delay docs.microsoft.com/en-us/dotnet/api/… et faites défiler vers le bas pour les remarques.
Dorus
28

si le thread actuel est tué et que vous l'utilisez Thread.Sleepet qu'il s'exécute, vous pouvez obtenir un ThreadAbortException. Avec Task.Delayvous pouvez toujours fournir un jeton d'annulation et le tuer gracieusement. C'est une des raisons pour lesquelles je choisirais Task.Delay. voir http://social.technet.microsoft.com/wiki/contents/articles/21177.visual-c-thread-sleep-vs-task-delay.aspx

Je conviens également que l'efficacité n'est pas primordiale dans ce cas.

Balanikas
la source
2
Supposons que nous avons la situation suivante: await Task.Delay(5000). Quand je tue la tâche, j'obtiens TaskCanceledException(et la supprime) mais mon fil est toujours vivant. Soigné! :)
AlexMelw
24

Je veux ajouter quelque chose. En fait, Task.Delayest un mécanisme d'attente basé sur une minuterie. Si vous regardez la source, vous trouverez une référence à une Timerclasse qui est responsable du retard. D'un autre côté, Thread.Sleeple thread actuel se met en veille, de cette façon vous bloquez et gaspillez un thread. Dans le modèle de programmation asynchrone, vous devez toujours utiliser Task.Delay()si vous voulez que quelque chose (suite) se produise après un certain retard.

crypté
la source
'wait Task.Delay ()' libère le thread pour faire d'autres choses jusqu'à l'expiration du temporisateur, 100% clair. Mais que se passe-t-il si je ne peux pas utiliser 'wait' puisque la méthode n'est pas préfixée avec 'async'? Ensuite, je peux seulement appeler «Task.Delay ()». Dans ce cas le thread est toujours bloqué mais j'ai l' avantage d'annuler le Delay () . Est-ce exact?
Erik Stroeken
5
@ErikStroeken Vous pouvez transmettre des jetons d'annulation au thread et à la tâche. Task.Delay (). Wait () se bloquera, tandis que Task.Delay () crée simplement la tâche si elle est utilisée sans attendre. Ce que vous faites avec cette tâche dépend de vous, mais le fil continue.