Je me sens comme si je devrais connaître la réponse à cela, mais je vais demander quand même au cas où je fais une erreur potentiellement catastrophique.
Le code suivant s'exécute comme prévu sans erreur / exceptions:
static void Main(string[] args)
{
ManualResetEvent flag = new ManualResetEvent(false);
ThreadPool.QueueUserWorkItem(s =>
{
flag.WaitOne();
Console.WriteLine("Work Item 1 Executed");
});
ThreadPool.QueueUserWorkItem(s =>
{
flag.WaitOne();
Console.WriteLine("Work Item 2 Executed");
});
Thread.Sleep(1000);
flag.Set();
flag.Close();
Console.WriteLine("Finished");
}
4 Réponses :
Ça ne va pas. Vous avez de la chance ici parce que vous n'avez commencé que deux threads. Ils commenceront immédiatement à courir lorsque vous appelez défini sur une machine à double noyau. Essayez cela à la place et regardez la bombe:
static void Main(string[] args) {
ManualResetEvent flag = new ManualResetEvent(false);
for (int ix = 0; ix < 10; ++ix) {
ThreadPool.QueueUserWorkItem(s => {
flag.WaitOne();
Console.WriteLine("Work Item Executed");
});
}
Thread.Sleep(1000);
flag.Set();
flag.Close();
Console.WriteLine("Finished");
Console.ReadLine();
}
Ceci est complètement correct, mais il fait bombarder en raison du code de test, non pas en raison du comportement de définir code>. Dans ce cas, l'événement est fermé avant que certains threads ont même a commencé i> en cours d'exécution. L'augmentation de l'impulsion de sommeil à 5 secondes rend cette fonctionnalité bien sur un quad noyau. Je peux empêcher le scénario ci-dessus en sérialisant l'accès à l'événement et en l'annulant immédiatement après la fermeture; Je suis principalement préoccupé par les threads qui attendent déjà sur la poignée d'attente.
Il n'y a aucune garantie que ce qu'un fil commence à fonctionner après un long sommeil arbitraire. Dormir plus longtemps ne réduit que le risque d'une exception ObjectDisposedException, il ne peut pas l'éliminer. Ces types de dépendances de synchronisation sont le clou ultime du cercueil de quiconque faisant des hypothèses sur le filetage.
Bien sûr, je n'utiliserais pas thread.sleep code> dans le code de production - la mise en œuvre réelle a utilisé divers primitives de synchronisation. Mais il s'est avéré être une condition de course subtile, qui a maintenant été corrigée. Merci pour votre contribution; +1 pour être le premier à l'appeler.
Ma prise est qu'il y a une condition de course. Avoir des objets d'événement écrits basés sur des variables de condition, vous obtenez du code comme celui-ci:
mutex.lock();
while (!signalled)
condition_variable.wait(mutex);
mutex.unlock();
Lorsque vous signalez avec un Il existe bien sûr des circonstances lorsque vous pourriez définir le drapeau et que votre fil ne le voit pas parce que cela fait du travail avant qu'il ne vérifie le drapeau (ou commevus suggéré si vous êtes Création de threads multiples): P> manuelResetevent.set code>, vous êtes garanti que tous les threads qui attendent cet événement (c'est-à-dire dans un état de blocage sur flag.waitone code>) Soyez signalé avant de renvoyer le contrôle à l'appelant. // In the Producer
ManualResetEvent flag = new ManualResetEvent(false);
CountdownLatch countdown = new CountdownLatch(0);
int numConsumers = 0;
while(hasMoreWork)
{
Consumer consumer = new Consumer(coutndown, flag);
// Create a new thread with each consumer
numConsumers++;
}
countdown.Reset(numConsumers);
flag.Set();
countdown.Wait();// your producer waits for all of the consumers to finish
flag.Close();// cleanup
Cela semble être une meilleure option de concept; J'ai du mal à comprendre comment il s'applique à la situation d'un seul producteur avec plusieurs consommateurs (cela semble axé sur plusieurs producteurs, consommateur unique). Pouvez-vous expliquer davantage comment ce verrou serait utilisé dans ce scénario?
J'ai mis à jour ma réponse pour refléter le producteur unique / multiple consommateur.
Ok, je l'ai eu. Doivent utiliser l'événement et i> le loquet. Malheureusement, le "producteur" dans ce cas n'aura aucune idée du nombre de consommateurs il y aura, la réalité est beaucoup plus compliquée que le code de test ... mais je pourrais peut-être adapter cela en quelque sorte. J'accepterai celui-ci s'il s'avère être la meilleure réponse après un petit moment.
@Aronnaugers Si vous ne savez pas combien de consommateurs seront créés et que vous vous attendez à ce que le système soit plus compliqué, vous pouvez regarder l'exemple de threadpool (vous pouvez attendre plusieurs éléments manuels à Westall (tableau
@Aaronaugers J'ai également mis à jour l'exemple pour refléter une situation où vous ne savez pas combien de consommateurs vous aurez ... cela devrait être complètement en sécurité maintenant.
Le producteur et le consommateur sont en fait assez lointain et le producteur n'a vraiment aucun moyen de suivre les consommateurs - cependant, ils acquièrent un objet partagé que j'ai le contrôle, et je pense que ce que je peux faire, c'est avoir tous les threads, y compris I> Le producteur, le loquet à l'heure d'acquisition et l'ONU-Lochez à la fin de la fin, et quiconque l'utilise pour la dernière fois drainer le compteur entraînant l'objet partagé de fermer son événement interne. Va le tester maintenant. Merci encore!
@Aaronaugers Il semble que vous ayez vos mains pleines :), mais faites-moi savoir si vous avez toujours des problèmes: je ferai de mon mieux pour vous aider. Bonne chance! :)
Nah, je vais bien, il suffit de passer à travers une batterie de tests unitaires pour vérifier que rien ne fuit et / ou échoue, cela semble bien. Une fois que j'ai eu le concept de, er, appelons-le à l'inversion du contrôle de la concurrence, les morceaux sont venus ensemble assez vite. Cheers à nouveau, le point est à vous.
"Vous êtes assuré que tous les threads qui attendent cet événement seront signalés avant de renvoyer le contrôle à l'appelant" -> Je ne trouve pas de référence pour cela ... aussi, mes tests me disent le contraire. Itéréter le démarrage de 4 threads avec une serviette () et une console.writine ("foo"). Puis réglez () le MRE et réinitialisez immédiatement (). Vous remarquerez que tous les fils d'attente ne sont pas libérés (parfois même pas un seul). Ou je manque quelque chose?
@Vincent van denberghe, "puis réglé () le MRE et réinitialiser immédiatement ()." C'est l'affaire d'exception: "Si deux appels sont trop proches, de sorte que le deuxième appel survient avant que d'un thread ait été publié, un seul thread est libéré. C'est comme si le deuxième appel ne s'est pas produit." ( Comme indiqué dans la documentation )
Intéressant que l'équipe-cadre pensait évidemment que cela était assez commun / important pour Ajouter un compte à rebours Primitif dans .NET 4 . Je recommanderais probablement que pour les futures implémentations, par opposition à la mise en œuvre manuelle d'un loquet.
ressemble à un motif risqué pour moi, même si cela est dû à la mise en œuvre [actuelle], cela va bien. Vous essayez de disposer d'une ressource qui peut toujours être utilisée. p>
C'est comme un nouveau départ et la construction d'un objet et la supprime aveuglément, même avant que les consommateurs de cet objet ne soit effectué. P>
Une expérience intéressante serait de déplacer le sommeil pour se produire juste avant la serviette (). Cela vous permettrait de tester ce que Watile () fait quand "drapeau" a été proche () 'd.
@PLILLIP NGAN: Il est effectivement indéfini, comme l'indique la documentation. Si vous déplacez le
Fermer code> dans le code avant le fichierthread.sleep code>, vous obtenez unObjectDisPosException code> ("La poignée sûre a été fermée") pendant leService d'attente code>. D'autre part, si vous déplacezFermer code> directement après i> le fichierthread.sleep code>, les deux threads sont signalés et l'exécution se termine avec succès. Donc, comportement non défini, il y a une condition de course dans le code "mauvais". Je veux juste m'assurer que je n'ai pas un comportement indéfini similaire dans ce que je pense, c'est mon "bon" code. :)