Solution de contournement pour le WaitHandle.WaitAll 64 poignée de limite?
Mon application génère des charges de différentes petites threads via ThreadPool.QueueUserWorkItem
qui j'ai garder une trace de la via de multiples ManualResetEvent
instances. J'utilise le WaitHandle.WaitAll
méthode pour bloquer ma demande de fermeture jusqu'à ce que ces fils ont terminé.
Je n'ai jamais eu de problèmes avant, toutefois, que ma demande est venue en vertu de charge en plus c'est à dire plus de threads en cours de création, je suis maintenant commence à prendre de cette exception:
WaitHandles must be less than or equal to 64 - missing documentation
Quelle est la meilleure solution pour cela?
Extrait de Code
List<AutoResetEvent> events = new List<AutoResetEvent>();
//multiple instances of...
var evt = new AutoResetEvent(false);
events.Add(evt);
ThreadPool.QueueUserWorkItem(delegate
{
//do work
evt.Set();
});
...
WaitHandle.WaitAll(events.ToArray());
Solution
int threadCount = 0;
ManualResetEvent finished = new ManualResetEvent(false);
...
Interlocked.Increment(ref threadCount);
ThreadPool.QueueUserWorkItem(delegate
{
try
{
//do work
}
finally
{
if (Interlocked.Decrement(ref threadCount) == 0)
{
finished.Set();
}
}
});
...
finished.WaitOne();
- Si et quand vous vous déplacez .NET 4 prendre un coup d'oeil à CountdownEvent. Il termine le comptage dans un bon paquet.
Vous devez vous connecter pour publier un commentaire.
Créer une variable qui garde la trace du nombre de tâches en cours d'exécution:
Créer un signal:
Décrémenter le nombre de tâches à chaque fois qu'une tâche est terminée:
Si il n'y a pas de tâche qui reste à accomplir, régler le signal:
Pendant ce temps, ailleurs, d'attendre le signal pour définir:
// do work
) peut lever une exception. Ainsi, vous devez vous assurer que, si une exception est levée, le numberOfTasks est correctement décrémenté (try ... finally
). Ne vous inquiétez pas que votre UserWorkItem peut ne pas être exécuté - il. Finalement.De départ avec .NET 4.0, vous avez deux de plus (et de l'OMI, plus propre) options disponibles pour vous.
La première consiste à utiliser la
CountdownEvent
de la classe. Ça évite d'avoir à gérer l'incrémentation et de décrémentation sur votre propre:Cependant, il y a une même solution plus robuste, et que l'utilisation de la
Tâche
de la classe, comme suit:À l'aide de la
Task
de la classe et de l'appel àWaitAll
est beaucoup plus propre, de l'OMI, comme vous allez le tissage moins primitives de thread tout au long de votre code (avis, pas d'attente poignées); vous n'avez pas à mettre en place un compteur de gérer l'incrémentation/décrémentation, que vous venez de configurer vos tâches et puis attendre sur eux. Cela permet au code d'être plus expressif dans le ce de ce que vous voulez faire et de ne pas les primitives de comment (au moins, en termes de gestion de la parallélisation de celui-ci)..NET 4.5 offre encore plus d'options, vous pouvez simplifier la génération de la séquence de
Task
des instances en appelant le statiqueExécuter
sur leTâche
de la classe:Ou, vous pouvez prendre avantage de la TPL bibliothèque de Flux de données (c'est dans la
System
espace de noms, donc, c'est officiel, même si c'est un téléchargement à partir de NuGet, comme Entity Framework) et utiliser unActionBlock<TInput>
, comme suit:Noter que le
ActionBlock<TInput>
par des processus par défaut un élément à la fois, donc si vous voulez l'avoir procédé à plusieurs actions à la fois, vous devez définir le nombre de connexions simultanées éléments que vous souhaitez traiter dans le constructeur par le passage d'unExecutionDataflowBlockOptions
instance et le réglage de laMaxDegreeOfParallelism
de la propriété:Si votre action est vraiment pas thread-safe, vous pouvez alors régler le
MaxDegreeOfParallelsim
propriétéDataFlowBlockOptions.Unbounded
:Le point de l'être, vous avez un contrôle plus fin sur comment parallèle, vous voulez que vos options.
Bien sûr, si vous avez une séquence d'éléments que vous souhaitez passé dans votre
ActionBlock<TInput>
exemple, vous pouvez lier unISourceBlock<TOutput>
mise en œuvre de nourrir laActionBlock<TInput>
, comme suit:En fonction de ce que vous devez faire, le TPL Dataflow bibliothèque devient un beaucoup option plus intéressante, en ce qu'il traite de l'accès concurrentiel à travers tous les tâches liées ensemble, et il vous permet d'être très précis sur juste comment parallèle, vous voulez que chaque morceau, tout en conservant une bonne séparation des préoccupations de chaque bloc.
Votre solution de contournement n'est pas correct. La raison en est que la
Set
etWaitOne
pourraient course si le dernier élément de travail provoque lathreadCount
pour aller à zéro avant la mise en attente du thread a eu de la chance de file d'attente tous éléments de travail. La solution est simple. Traiter votre mise en attente du thread, comme si il s'agissait d'un élément de travail lui-même. InitialiserthreadCount
à 1 et ne une diminution du signal et quand la queue est terminée.Comme une préférence personnelle, j'ai comme l'utilisation de la
CountdownEvent
classe pour faire le calcul pour moi.Ajoutant à dtb la réponse que vous pouvez emballer dans un joli simple de la classe.
Ajoutant à dtb réponse quand on veut avoir des rappels.
Je ne l'ai résolu: il suffit de la pagination, le nombre d'événements à attendre sans trop de performace perdu, et il fonctionne parfaitement sur l'environnement de production. Suit le code:
SP3 de Windows XP prend en charge un maximum de deux WaitHandles. Pour les cas plus de 2 WaitHandles Application met fin prématurément.