web-dev-qa-db-fra.com

Que se passe-t-il avec Task.Delay (). Wait ()?

Je suis confus pourquoi Task.Delay().Wait() prend 4x plus de temps , alors Thread.Sleep()

Par exemple. task-00 s'exécutait sur uniquement le thread 9 et a pris 2193ms ? Je suis conscient que l'attente de synchronisation est mauvaise dans les tâches, car l'ensemble du thread est bloqué. C'est juste pour le test.

Test simple en application console: 

bool flag = true;
var sw = Stopwatch.StartNew();
for (int i = 0; i < 10; i++)
{
    var cntr = i;
    {
        var start = sw.ElapsedMilliseconds;
        var wait = flag ? 100 : 300;
        flag = !flag;

        Task.Run(() =>
        {
            Console.WriteLine($"task-{cntr.ToString("00")} \t ThrID: {Thread.CurrentThread.ManagedThreadId.ToString("00")},\t Wait={wait}ms, \t START: {start}ms");                     
            //Thread.Sleep(wait);
            Task.Delay(wait).Wait();
            Console.WriteLine($"task-{cntr.ToString("00")} \t ThrID: {Thread.CurrentThread.ManagedThreadId.ToString("00")},\t Wait={wait}ms, \t END: {sw.ElapsedMilliseconds}ms");
            ;
        });
    }
}
Console.ReadKey();
return;

Avec Task.Delay().Wait():
task-03 ThrID: 05, Wait = 300ms, START: 184ms
task-04 ThrID: 07, Wait = 100ms, START: 184ms
task-00 ThrID: 09, Wait = 100ms, START: 0ms
task-06 ThrID: 04, Wait = 100ms, START: 185ms
task-01 ThrID: 08, Wait = 300ms, START: 183ms
task-05 ThrID: 03, Wait = 300ms, START: 185ms
task-02 ThrID: 06, Wait = 100ms, START: 184ms
task-07 ThrID: 10, Wait = 300ms, START: 209 ms
task-07 ThrID: 10, Wait = 300ms, FIN: 1189ms
task-08 ThrID: 12, Wait = 100ms, START: 226ms
task-09 ThrID: 10, Wait = 300ms, START: 226ms
task-09 ThrID: 10, Wait = 300ms, FIN: 2192ms
task-06 ThrID: 04, Wait = 100ms, FIN: 2193ms
task-08 ThrID: 12, Wait = 100ms, FIN: 2194ms
task-05 ThrID: 03, Wait = 300ms, FIN: 2193ms
task-03 ThrID: 05, Wait = 300ms, FIN: 2193ms
task-00 ThrID: 09, Wait = 100ms, FIN: 2193ms
task-02 ThrID: 06, Wait = 100ms, FIN: 2193ms
task-04 ThrID: 07, Wait = 100ms, FIN: 2193ms
task-01 ThrID: 08, Wait = 300ms, FIN: 2193ms 

Avec Thread.Sleep():
task-00 ThrID: 03, Wait = 100ms, START: 0ms
task-03 ThrID: 09, Wait = 300ms, START: 179ms
task-02 ThrID: 06, Wait = 100ms, START: 178ms
task-04 ThrID: 08, Wait = 100ms, START: 179ms
task-05 ThrID: 04, Wait = 300ms, START: 179ms
task-06 ThrID: 07, Wait = 100ms, START: 184ms
task-01 ThrID: 05, Wait = 300ms, START: 178ms
task-07 ThrID: 10, Wait = 300ms, START: 184ms
task-00 ThrID: 03, Wait = 100ms, FIN: 284ms
task-08 ThrID: 03, Wait = 100ms, START: 184ms
task-02 ThrID: 06, Wait = 100ms, FIN: 285ms
task-09 ThrID: 06, Wait = 300ms, START: 184ms
task-04 ThrID: 08, Wait = 100ms, FIN: 286ms
task-06 ThrID: 07, Wait = 100ms, FIN: 293ms
task-08 ThrID: 03, Wait = 100ms, FIN: 385ms
task-03 ThrID: 09, Attendre = 300ms, FIN: 485ms
task-05 ThrID: 04, Wait = 300ms, FIN: 486ms
task-01 ThrID: 05, Attente = 300ms, FIN: 493ms
task-07 ThrID: 10, Wait = 300ms, FIN: 494ms
task-09 ThrID: 06, Wait = 300ms, FIN: 586ms 

Modifier:
Avec async lambda et awaitTask.Delay() est aussi rapide que Thread.Sleep(), peut être aussi plus rapide (511 ms).
Edit 2:
Avec ThreadPool.SetMinThreads(16, 16);, Task.Delay().Wait() fonctionne aussi vite que Thread.Sleep pour 10 itérations dans la boucle. Avec plus d'itérations, c'est encore plus lent. Il est également intéressant de noter que si, sans ajustement, j'augmente le nombre d'itérations pour Thread.Sleep à 30 , c'est toujours plus rapide, puis 10 itération avec Task.Delay().Wait()
Edit 3:
La surcharge Task.Delay(wait).Wait(wait) fonctionne aussi vite que Thread.Sleep()

10
Rekshino

J'ai un peu réécrit l'extrait posté pour que les résultats soient mieux ordonnés. Mon tout nouvel ordinateur portable a trop de cœurs pour interpréter assez bien la sortie existante mélangée. Enregistrer les heures de début et de fin de chaque tâche et les afficher une fois qu'elles sont toutes terminées. Et enregistrer l'heure de début réelle de la tâche. J'ai eu:

0: 68 - 5031
1: 69 - 5031
2: 68 - 5031
3: 69 - 5031
4: 69 - 1032
5: 68 - 5031
6: 68 - 5031
7: 69 - 5031
8: 1033 - 5031
9: 1033 - 2032
10: 2032 - 5031
11: 2032 - 3030
12: 3030 - 5031
13: 3030 - 4029
14: 4030 - 5031
15: 4030 - 5031

Ah, ça a soudainement beaucoup de sens. Un motif à toujours surveiller lorsqu’il s’agit de thread threadpool. Notez qu’une fois par seconde, quelque chose de significatif se produit et que deux threads tp commencent à s’exécuter et que certains d’entre eux peuvent se terminer.

Ceci est un scénario de blocage similaire à le présent Q + A mais sans le résultat plus désastreux du code de cet utilisateur. La cause est presque impossible à voir car il est enterré dans le code .NETFramework, vous devez regarder comment Task.Delay () est implémenté pour le comprendre.

Le code approprié est ici , notez comment il utilise un System.Threading.Timer pour implémenter le délai. Un détail important concernant ce minuteur est que son rappel est exécuté sur le pool de threads. Quel est le mécanisme de base par lequel Task.Delay () peut implémenter la promesse "vous ne payez pas pour ce que vous n'utilisez pas".

Le détail est que cela peut prendre un certain temps si le pool de threads est occupé à traiter les demandes d'exécution du pool de threads. Ce n'est pas que la minuterie est lente, le problème est que la méthode de rappel ne démarre pas assez tôt. Le problème dans ce programme, Task.Run () a ajouté un tas de demandes, plus que ne peuvent être exécutés en même temps. Le blocage se produit parce que le tp-thread qui a été démarré par Task.Run () ne peut pas terminer l'appel Wait () tant que le rappel du minuteur ne s'est pas exécuté.

Vous pouvez créer un blocage difficile qui bloque le programme pour toujours en ajoutant ce morceau de code au début de Main ():

     ThreadPool.SetMaxThreads(Environment.ProcessorCount, 1000);

Mais le max-thread normal est beaucoup plus élevé. Dont le gestionnaire de pool de threads tire parti pour résoudre ce type de blocage. Une fois par seconde, cela permet à deux threads de plus que le nombre "idéal" de s'exécuter lorsque ceux existants ne sont pas terminés. C'est ce que vous voyez dans la sortie. Mais il n’est que deux à la fois, ce qui est insuffisant pour réduire considérablement les 8 threads occupés qui sont bloqués dans l’appel Wait ().

L'appel Thread.Sleep () n'a pas ce problème, il ne dépend pas du code .NETFramework ou du pool de threads à terminer. C'est le planificateur de threads du système d'exploitation qui s'en occupe, et il fonctionne toujours en raison de l'interruption de l'horloge. Cela permet ainsi aux nouveaux threads de commencer à s’exécuter toutes les 100 ou 300 ms au lieu d’une fois par seconde.

Difficile de donner des conseils concrets pour éviter un tel piège. Outre le conseil universel, évitez toujours de bloquer les threads de travail.

6
Hans Passant

Ni Thread.Sleep(), ni Task.Delay() ne garantissent que l'interne sera correct. 

Thread.Sleep() work Task.Delay() de manière très différente. Thread.Sleep() bloque le thread actuel et l'empêche d'exécuter du code. Task.Delay() crée une minuterie qui cochonne à l'expiration du délai et l'assigne à l'exécution sur le pool de threads.

Vous exécutez votre code en utilisant Task.Run(), qui créera des tâches et les mettra en file d'attente sur le pool de threads. Lorsque vous utilisez Task.Delay(), le thread actuel est libéré sur le pool de threads et il peut commencer à traiter une autre tâche. De cette manière, plusieurs tâches démarreront plus rapidement et vous enregistrez les temps de démarrage pour tous. Ensuite, lorsque les minuteries commencent à se mettre en marche, elles épuisent également le pool et certaines tâches sont plus longues à terminer qu’elles ne l’ont commencé. C'est pourquoi vous enregistrez de longs temps.

Lorsque vous utilisez Thread.Sleep(), vous bloquez le thread en cours sur le pool et il est impossible de traiter davantage de tâches. Le pool de threads ne se développe pas immédiatement, donc de nouvelles tâches attendent. Par conséquent, toutes les tâches sont exécutées à peu près au même moment, ce qui vous semble plus rapide.

EDIT: vous utilisez Task.Wait(). Dans votre cas, Task.Wait () essaie d'inclure l'exécution sur le même thread. En même temps, Task.Delay() s'appuie sur un minuteur exécuté sur le pool de threads. Une fois que vous appelez Task.Wait() vous bloquez un thread de travail du pool, vous avez ensuite besoin d'un thread disponible sur le pool pour terminer l'opération de la même méthode de travail. Lorsque vous awaitle Delay(), une telle alignement n'est pas nécessaire et que le thread de travail est immédiatement disponible pour traiter les événements du minuteur. Lorsque vous Thread.Sleep, vous ne disposez pas d'un minuteur pour terminer la méthode de travail.

Je crois que c’est ce qui cause la différence de temps considérable.

4
Nick

Votre problème est que vous mélangez du code asynchrone avec du code synchrone sans utiliser async et await. N'utilisez pas l'appel synchrone .Wait, il bloque votre thread et c'est pourquoi le code asynchrone Task.Delay() ne fonctionne pas correctement.

Le code asynchrone ne fonctionne souvent pas correctement lorsqu'il est appelé de manière synchrone, car il n'est pas conçu pour fonctionner de cette façon. Vous pouvez avoir de la chance et le code asynchrone semble fonctionner lorsqu’il est exécuté de manière synchrone. Mais si vous utilisez une bibliothèque externe, l’auteur de cette bibliothèque peut modifier son code de manière à rompre votre code. Le code asynchrone doit être complètement asynchrone.

Le code asynchrone est généralement plus lent que le code synchrone. Mais l'avantage est qu'il s'exécute de manière asynchrone, par exemple si votre code attend que le fichier soit chargé, un autre code peut être exécuté sur le même processeur lorsque le fichier est chargé.

Votre code devrait ressembler à celui ci-dessous, mais avec async, vous ne pouvez pas être sûr que ManagedThreadId restera le même. Parce que le fil en cours d'exécution de votre code peut changer pendant l'exécution. Vous ne devez jamais utiliser la propriété ManagedThreadId ou l'attribut [ThreadStatic] si vous utilisez quand même du code asynchrone pour cette raison.

Async/Await - Meilleures pratiques en programmation asynchrone

bool flag = true;
var sw = Stopwatch.StartNew();
for (int i = 0; i < 10; i++)
{
    var cntr = i;
    {
        var start = sw.ElapsedMilliseconds;
        var wait = flag ? 100 : 300;
        flag = !flag;

        Task.Run(async () =>
        {
            Console.WriteLine($"task-{cntr.ToString("00")} \t ThrID: {Thread.CurrentThread.ManagedThreadId.ToString("00")},\t Wait={wait}ms, \t START: {start}ms");
            await Task.Delay(wait);
            Console.WriteLine($"task-{cntr.ToString("00")} \t ThrID: {Thread.CurrentThread.ManagedThreadId.ToString("00")},\t Wait={wait}ms, \t END: {sw.ElapsedMilliseconds}ms");
        });
    }
}
Console.ReadKey();
return;
0
Wanton