en utilisant wait Task.Delay dans une performance for kills

Disons que je veux commencer approximativement N tâches par seconde réparties de manière égale.

Alors j’ai essayé ceci:

public async Task Generate(int numberOfCallsPerSecond) { var delay = TimeSpan.FromMiliseconds(1000/numberOfCallsPerSecond); // a call should happen every 1000 / numberOfCallsPerSecond miliseconds for (int i=0; i < numberOfcallsPerSecond; i++) { Task t = Call(); // don't wait for result here await Task.Delay(delay); } } 

Au début, je m’attendais à ce que cela fonctionne en 1 seconde, mais pour numberOfCallsPerSecond = 100 cela prend 16 seconds sur mon processeur à 12 cœurs. Il semble que la tâche attendue.Delay ajoute beaucoup de temps système (bien sûr, sans la génération en place des appels se produit en 3ms).

Je ne m’attendais pas à ce que l’attente ajoute autant de frais généraux dans ce scénario. Est-ce normal?

MODIFIER:

S’il vous plaît oublier l’appel (). L’exécution de ce code montre un résultat similaire:

 public async Task Generate(int numberOfCallsPerSecond) { var delay = TimeSpan.FromMiliseconds(1000/numberOfCallsPerSecond); // a call should happen every 1000 / numberOfCallsPerSecond miliseconds for (int i=0; i < numberOfcallsPerSecond; i++) { await Task.Delay(delay); } } 

J’ai essayé de l’exécuter avec numberOfCallsPerSecond = 500 et cela prend environ 10 secondes. Je m’attendais à ce que Generate prenne environ 1 seconde, et non 10 fois plus.

Task.Delay est léger mais pas précis. Étant donné que la boucle se termine sans délai beaucoup plus rapidement, il semble que votre fil de discussion devienne inactif et que vous utilisiez un mode de veille du système d’exploitation pour attendre que la timer s’écoule. Le minuteur est vérifié en fonction du quantum de planification de threads de système d’exploitation (dans le même gestionnaire d’interruptions qui effectue la préemption de threads), soit 16 ms par défaut.

Vous pouvez réduire le quantum avec timeBeginPeriod , mais une meilleure approche (plus efficace en timeBeginPeriod puissance) si vous avez besoin de limiter le débit plutôt que le chronométrage exact consiste à garder une trace du temps écoulé (la classe Stopwatch est bonne pour cela) et du nombre d’appels effectués. délai lorsque les appels effectués ont rattrapé le temps écoulé. L’effet général est que votre thread se réveillera ~ 60 fois par seconde et lancera quelques éléments de travail à chaque fois. Si votre processeur est occupé par quelque chose d’autre, vous commencerez à exécuter des tâches supplémentaires lorsque vous aurez le contrôle – bien qu’il soit également assez simple de limiter le nombre d’éléments démarrés à la fois, si c’est ce dont vous avez besoin.

 public async Task Generate(int numberOfCallsPerSecond) { var elapsed = Stopwatch.StartNew(); var delay = TimeSpan.FromMiliseconds(1000/numberOfCallsPerSecond); // a call should happen every 1000 / numberOfCallsPerSecond miliseconds for (int i=0; i < numberOfcallsPerSecond; i++) { Call(); // don't wait for result here int expectedI = elapsed.Elapsed.TotalSeconds * numberOfCallsPerSecond; if (i > expectedI) await Task.Delay(delay); } } 

Mon débogueur psychique dit que votre méthode Call comporte une partie synchrone significative (c’est-à-dire la partie précédant une await ) qui prend du temps pour s’exécuter de manière synchrone.

Si vous souhaitez que la méthode Generate «lance» uniquement ces appels et les exécute simultanément (y compris les parties synchrones), vous devez les décharger vers un thread Task.Run aide de Task.Run :

 var task = Task.Run(() => Call()); await Task.Delay(delay); 

Task.Delay ajoute presque pas de frais généraux. Il utilise un System.Threading.Timer interne qui nécessite très peu de ressources.

Si vous utilisez un intervalle de temps avec Task.Delay (), le processeur sera tué. Utilisez un entier et ça ne va pas. Histoire vraie. Je ne sais pas pourquoi.