web-dev-qa-db-fra.com

Quand utiliser TaskCreationOptions.LongRunning?

Je me le demande depuis un bon moment, mais je n'ai jamais vraiment trouvé la réponse.

Je comprends que c'est un indice pour le planificateur de tâches sur lequel la tâche s'exécutera et que le planificateur de tâches peut (ou de nos jours?) Décider d'instancier un thread non-pool de threads pour cette tâche.

Ce que je ne sais pas (et, étonnamment, que je ne trouve nulle part sur Internet), c'est une "règle de base" quand spécifier une tâche comme étant longue. Est-ce qu'une seconde est longue? 30 secondes? Une minute? 5 minutes? At-il une relation avec la quantité de tâches que l'application utilise? Dois-je, en tant que programmeur, faire un calcul avec les #threads dans le pool de threads, combien de tâches je crée, combien seront de longue durée en même temps, et en fonction de cela, prendre la décision d'utiliser une tâche de longue durée?

J'espère apprendre quelque chose ici.

28
bas

Il peut être quantifié, le gestionnaire de pool de threads ajoute un thread supplémentaire au-delà de l'optimum lorsque les threads tp existants ne se terminent pas assez tôt. Il le fait deux fois par seconde, jusqu'au maximum fixé par SetMaxThreads (). Qui a une très valeur par défaut élevée. L'optimum est le nombre de cœurs de processeur dont dispose la machine, 4 est typique. L'exécution de plus de threads que de cœurs disponibles peut être néfaste en raison de la surcharge de changement de contexte.

Il le fait sur la base du hypothèse que ces threads existants ne progressent pas car ils n'exécutent pas suffisamment de code. En d'autres termes, ils bloquent trop les E/S ou un verrou. De tels threads n'utilisent donc pas les cœurs assez efficacement et permettre l'exécution d'un thread supplémentaire est tout à fait approprié pour augmenter l'utilisation du processeur et faire plus de travail.

Il est donc "long" lorsque le thread prend plus d'une demi-seconde. Gardez à l'esprit que c'est très long, cela équivaut à environ 4 milliards d'instructions de processeur sur une machine de classe de bureau moderne. À moins que vous exécutiez un code de calcul lourd comme le calcul de la valeur de pi à un milliard de chiffres, exécutant ainsi réellement ces 4 milliards d'instructions, un thread pratique ne peut prendre autant de temps lorsqu'il fait bloque trop souvent. Ce qui est très courant, quelque chose comme une requête dbase est souvent lente et exécutée sur un thread de travail et consomme peu de CPU.

Il vous appartient par ailleurs de vérifier que l'hypothèse que le gestionnaire de pool de threads émettra est exacte. La tâche devrait prendre beaucoup de temps car elle n'utilise pas efficacement le processeur. Le Gestionnaire des tâches est un moyen simple de voir ce que les cœurs de processeur font dans votre programme, bien qu'il ne vous indique pas exactement quel code ils exécutent. Vous aurez besoin d'un test unitaire pour voir le thread s'exécuter de manière isolée. Le moyen ultime et uniquement complètement précis de vous dire que l'utilisation de LongRunning était un choix approprié est de vérifier que votre application fait effectivement plus de travail.

25
Hans Passant

Modification de la réponse de Hans avec laquelle je suis en grande partie d'accord.

La raison la plus importante pour spécifier LongRunning est d'obtenir une exécution pratiquement garantie et presque immédiate. Il n'y aura pas d'attente pour que le pool de threads émette un thread vers votre élément de travail. Je dis "pratiquement" car le système d'exploitation est libre de ne pas planifier votre thread. Mais vous obtiendrez une part du processeur et cela ne prendra généralement pas très longtemps avant que cela ne se produise.

Vous sautez devant la file d'attente en spécifiant LongRunning. Pas besoin d'attendre que le pool de threads émette 2 threads par seconde s'il est sous charge.

Vous utiliserez donc LongRunning pour des choses qui ne doivent pas nécessairement se produire de la manière la plus efficace, mais de manière rapide et régulière. Par exemple, certains travaux d'interface utilisateur, la boucle de jeu, les rapports de progression, ...

Le démarrage et l'arrêt d'un thread coûtent de l'ordre de 1 ms de temps CPU. C'est bien plus que l'émission d'éléments de travail de pool de threads. Je viens de comparer cela à des articles 3M émis et complétés par seconde. L'indice de référence était assez artificiel, mais l'ordre de grandeur est correct.

LongRunning est documenté comme un indice mais il est absolument efficace dans la pratique. Aucune heuristique ne tient compte de votre indice. Il est supposé être correct.

14
usr

quand spécifier une tâche comme étant longue

Cela dépend de la tâche à accomplir. Si la tâche contient while(true) {...} et dure jusqu'à l'arrêt de l'application, il est logique de spécifier LongRunning. Si vous créez une tâche pour mettre en file d'attente une opération et empêcher le blocage du thread en cours, cela ne vous dérange pas vraiment (ne spécifiez rien).

Cela dépend de ce que font les autres tâches. Peu importe que vous exécutiez quelques tâches avec ou sans LongRunning. Mais il pourrait être difficile de créer des milliers de tâches, où chaque demande un nouveau thread. Ou en face, vous pouvez rencontrer famine de fil sans le spécifier.

Une façon simple d'y penser est la suivante: préférez-vous qu'une nouvelle tâche s'exécute dans un nouveau thread ou vous ne vous en souciez pas? Si d'abord - alors utilisez LongRunningOption. Ceci ne signifie pas quelle tâche s'exécutera dans un autre thread, c'est juste un bon critère lorsque vous devez le spécifier.

Par exemple. lors de l'utilisation de ContinueWith alors LongRunning est opposé à ExecuteSynchronously (il y a un check pour éviter que les deux soient spécifiés). Si vous avez plusieurs continuations, vous pouvez éviter la surcharge de la file d'attente et exécuter une continuation spécifique dans le même thread ou le contraire - vous ne voulez pas qu'une des continuations interfère avec les autres et vous pouvez alors utiliser spécifiquement LongRunning. Reportez-vous à cet article (et this ) pour en savoir plus sur ExecuteSynchronously.

3
Sinatr

Une tâche longue est celle qui peut entrer dans un état d'attente, bloquant le thread sur lequel elle s'exécute, ou qui prend trop de temps CPU (nous y reviendrons).

Certains peuvent dire que cette définition est trop large, de nombreuses tâches seraient longues, mais pensez-y, même si l'attente est limitée à un petit délai, la tâche n'utilise toujours pas le processeur efficacement. Si le nombre de ces tâches augmente, vous constaterez qu'elles ne sont pas mises à l'échelle linéairement au-delà des MinWorkerThreads (voir ThreadPool.SetMinThreads ), la dégradation est vraiment mauvaise.

L'approche consiste à faire passer toutes les E/S (fichier, réseau, DB, etc.) pour qu'elles soient asynchrones.

Il y a aussi des tâches longues en raison de longs calculs gourmands en CPU.

L'approche consiste à différer le calcul, par exemple en insérant await Task.Yield() à certains points, ou de préférence différer le calcul explicitement en planifiant une tâche après l'autre, chacun traitant un bloc de données précédemment divisé ou traitant un tampon jusqu'à une limite limite de temps.

Le "trop ​​de temps" est à vous de décider.

Lorsque vous êtes dans un environnement où vous partagez le pool de threads, chaque fois que c'est trop de temps, vous devez choisir une valeur raisonnable.

Par exemple. dans ASP.NET sous IIS, voir quel est le temps moyen pris par demande pour les demandes les plus courantes. De même, dans un service où le pool de threads est utilisé, par exemple pour traiter une file d'attente de messages, prenez la moyenne par message.

Plus généralement, "trop de temps" signifie que les éléments de travail sont mis en file d'attente plus rapidement qu'ils ne sont traités. Il peut y avoir des rafales de travail, vous devez donc en faire la moyenne sur l'unité de temps qui vous importe, que ce soit une seconde, une minute, 10 minutes, etc. Lorsque vous avez un SLA, cet intervalle doit être défini quelque part.

Après avoir une valeur sensible, vous devez voir, en pratique, si vous pouvez l'augmenter ou si vous devez la diminuer. Plus souvent, si vous pouvez l'augmenter, vous feriez mieux de ne pas l'augmenter, sauf si vous pouvez voir une différence de performance significative. "Significatif" signifie que le nombre d'articles traités augmente de façon plus que linéaire, donc si c'est linéaire (ou en dessous de linéaire, cela peut arriver), ne le faites pas.


D'après mon expérience, si vous avez une tâche de longue haleine selon l'une de ces définitions, il vaut généralement mieux gérer votre propre thread ou ensemble de threads.

2
acelent