61 votes

Pourquoi le Mutex n'est-il pas libéré lorsqu'il est éliminé ?

J'ai le code suivant :

using (Mutex mut = new Mutex(false, MUTEX_NAME))
{
    if (mut.WaitOne(new TimeSpan(0, 0, 30)))
    {
       // Some code that deals with a specific TCP port
       // Don't want this to run at the same time in another process
    }
}

J'ai placé un point d'arrêt dans le if et exécuté le même code dans une autre instance de Visual Studio. Comme prévu, le .WaitOne blocs d'appels. Cependant, à ma grande surprise, dès que j'ai continuer en première instance et le using se termine, je reçois une exception dans le second processus à propos d'un Mutex abandonné.

La solution consiste à appeler ReleaseMutex :

using (Mutex mut = new Mutex(false, MUTEX_NAME))
{
    if (mut.WaitOne(new TimeSpan(0, 0, 30)))
    {
       // Some code that deals with a specific TCP port
       // Don't want this to run twice in multiple processes
    }

    mut.ReleaseMutex();
}

Maintenant, les choses fonctionnent comme prévu.

Ma question : Habituellement, le point d'une IDisposable c'est nettoie quel que soit l'état dans lequel vous mettez les choses. Je pourrais peut-être avoir plusieurs attend y libère dans un using mais lorsque l'identifiant du Mutex est éliminé, ne devrait-il pas être libéré automatiquement ? En d'autres termes, pourquoi dois-je appeler ReleaseMutex si je suis dans une using bloc ?

Je suis également préoccupé par le fait que, si le code dans l'application if le bloc se plante, j'aurai des mutex abandonnés qui traînent.

Y a-t-il un avantage à mettre Mutex dans un using bloc ? Ou, devrais-je simplement nouveau a Mutex l'envelopper dans un try/catch, et appeler ReleaseMutex() au sein de la enfin (qui met en œuvre exactement ce que je pensée Dispose() ferait)

0 votes

Sachant ce que je sais des mutex (conceptuellement), j'imagine que lorsque Dispose() est appelé sur le Mutex, il vérifie s'il est toujours utilisé. Si c'est le cas, j'imagine qu'il ne s'élimine pas lui-même (et il ne devrait pas !). J'aimerais cependant obtenir des informations supplémentaires à ce sujet, de la part de quelqu'un qui connaît mieux les mécanismes internes de l'environnement Mutex. Mutex en .Net.

0 votes

Le point de using est juste d'appeler Dispose à la fin. Il est traduit en try{ //yourcode } finally { yourobj.Dispose() } ... si vous avez besoin de plus de logique (comme la libération dans ce cas), vous devez la gérer vous-même.

0 votes

L'utilisez-vous dans plusieurs processus, ou êtes-vous seulement concerné par les fils multiples dans le même processus ?

64voto

voithos Points 15066

La documentation explique (dans la section "Remarques") qu'il y a une différence conceptuelle entre instanciation de un objet Mutex (qui fait no en fait, ne font rien de spécial en matière de synchronisation) et acquisition de un Mutex (en utilisant WaitOne ). Notez que :

  • WaitOne renvoie un booléen, ce qui signifie que l'acquisition d'un Mutex peut échouer (timeout) et les deux cas doivent être traités
  • Lorsque WaitOne renvoie à true alors le thread appelant a acquis le Mutex et doit appelez ReleaseMutex sinon le Mutex sera abandonné.
  • Quand il revient false puis le fil d'appel ne doit pas appelez ReleaseMutex

Les Mutex ne se limitent donc pas à l'instanciation. Quant à savoir si vous devez utiliser using Quoi qu'il en soit, jetons un coup d'oeil à ce que Dispose (comme hérité de WaitHandle ):

protected virtual void Dispose(bool explicitDisposing)
{
    if (this.safeWaitHandle != null)
    {
        this.safeWaitHandle.Close();
    }
}

Comme nous pouvons le voir, le Mutex est no mais il y a un peu de nettoyage à faire. using serait une bonne approche.

Pour ce qui est de la marche à suivre, vous pouvez bien sûr utiliser un système d'alerte précoce. try/finally pour s'assurer que, si le Mutex est acquis, il est correctement libéré. C'est probablement l'approche la plus simple.

Si usted realmente ne se préoccupe pas du cas où le Mutex ne peut pas être acquis (ce que vous n'avez pas indiqué, puisque vous passez une commande TimeSpan a WaitOne ), vous pourriez envelopper Mutex dans votre propre classe qui implémente IDisposable , acquérir le Mutex dans le constructeur (en utilisant WaitOne() sans argument), et le libérer dans Dispose . Cependant, je ne le recommanderais probablement pas, car cela ferait attendre vos fils indéfiniment si quelque chose se passait mal, et quoi qu'il en soit, il y a des possibilités d'utilisation de ces fils. de bonnes raisons pour traiter explicitement les deux cas lors d'une tentative d'acquisition, comme mentionné par @HansPassant.

7 votes

+1 - Excellente réponse ! Je pense que le fait que vous ne peut pas appelez ReleaseMutex si WaitOne a échoué signifie que Dispose ne peut pas à moins qu'il n'implémente également une logique de suivi des attentes réussies. Il semble que la solution consiste à mettre Mutex dans un bloc d'utilisation, et mettre un try/finally dans le bloc if bloc.

1 votes

Mike Christensen, ça pourrait juste throw au cas où il n'aurait pas réussi à acquérir le mutex dans son délai d'attente, ce qui est plus logique de toute façon puisqu'il indique un échec.

0 votes

Oui, si vous ne pouvez pas appeler ReleaseMutex, vous pouvez simplement le mettre dans un try { } à l'intérieur de votre dispose.

41voto

Hans Passant Points 475940

Cette décision de conception a été prise il y a très, très longtemps. Il y a plus de 21 ans, bien avant que .NET ne soit envisagé ou que la sémantique de IDisposable ne soit considérée. La classe Mutex de .NET est une emballage pour la prise en charge des mutex par le système d'exploitation sous-jacent. Le constructeur pinvokes CreateMutex la méthode WaitOne() évoque WaitForSingleObject() .

Notez la valeur de retour WAIT_ABANDONED de WaitForSingleObject(), c'est elle qui génère l'exception.

Les concepteurs de Windows ont mis en place la règle absolue selon laquelle un thread qui possède le mutex doit appeler ReleaseMutex() avant qu'il ne se termine. Et s'il ne le fait pas, c'est une indication très forte que le thread s'est terminé d'une manière inattendue, typiquement par une exception. Ce qui implique que la synchronisation est perdue, un bug de threading très sérieux. Comparez avec Thread.Abort(), une façon très dangereuse de terminer un thread dans .NET pour la même raison.

Les concepteurs de .NET n'ont en aucun cas modifié ce comportement. En effet, il n'existe aucun moyen de tester l'état du mutex autrement qu'en effectuant une attente. Vous pouvez consulter le site doit appelez ReleaseMutex(). Et notez que votre deuxième extrait n'est pas correct non plus ; vous ne pouvez pas l'appeler sur un mutex que vous n'avez pas acquis. Il doit être déplacé à l'intérieur du corps de l'instruction if().

0 votes

+1 - Merci pour l'histoire ! Pouvez-vous commenter le code que j'ai posté dans mon fichier réponse à cette question ? D'après ce que je peux dire, cela devrait être la façon idéale de procéder.

1 votes

Hmya, vous dissimulez le diagnostic que les concepteurs de Windows ont intégré à dessein. Votre code a planté alors qu'il possédait le mutex, vous ne pouvez plus raisonner sur l'état du programme. Les chances que vous puissiez récupérer sont extrêmement minces, en particulier parce que vous n'avez aucun autre moyen décent de signaler à l'autre processus qu'il doit arrêter d'utiliser la ressource partagée puisqu'il est dans un état totalement inconnu. Les exceptions sont une bonne chose, ne le faites pas.

0 votes

Donc, c'est mieux de le laisser se planter et d'abandonner le Mutex ? Ensuite, laisser l'autre WaitOne les appels se plantent aussi ?

8voto

Mike Christensen Points 29735

Ok, je poste une réponse à ma propre question. De ce que je peux dire, este est le moyen idéal de mettre en œuvre un Mutex que :

  1. On s'en débarrasse toujours
  2. Est libéré si WaitOne a réussi.
  3. Ne sera pas abandonné si un code quelconque lance une exception.

J'espère que cela aidera quelqu'un !

using (Mutex mut = new Mutex(false, MUTEX_NAME))
{
    if (mut.WaitOne(new TimeSpan(0, 0, 30)))
    {
        try
        {
           // Some code that deals with a specific TCP port
           // Don't want this to run twice in multiple processes        
        }
        catch(Exception)
        {
           // Handle exceptions and clean up state
        }
        finally
        {
            mut.ReleaseMutex();
        }
    }
}

Mise à jour : Certains diront que si le code dans le cadre de la try met votre ressource dans un état instable, vous devriez no libérer le Mutex et à la place le laisser être abandonné. En d'autres termes, il suffit d'appeler mut.ReleaseMutex(); lorsque le code se termine avec succès, et ne pas le mettre dans le cadre de la finally bloc. Le code acquérant le Mutex pourrait alors attraper cette exception et faire le bon choix .

Dans ma situation, je ne change pas vraiment d'état. J'utilise temporairement un port TCP et je ne peux pas faire tourner une autre instance du programme en même temps. Pour cette raison, je pense que ma solution ci-dessus est bonne, mais la vôtre peut être différente.

0 votes

Je ne sais pas pourquoi il a été rétrogradé, mais j'ai ajouté quelques détails supplémentaires. Peut-être que c'est juste considéré mauvaise forme pour répondre à votre propre question.

1 votes

Ce n'est pas mal de répondre à sa propre question ! Il a probablement été rétrogradé parce que le code est incorrect. Vous attrapez et avalez l'exception, ce qui cache un sérieux bug de threading - le genre de bug que vous ne voulez pas voir disparaître sous le tapis.

2 votes

@CodyGray Je vois que Mike ajoute une catch après votre commentaire. Mais est-ce qu'un bloc try/finally, sans aucune clause catch(){} L'exception pourrait être gérée plus haut dans la pile, et non avalée ? (Et si elle n'est pas attrapée du tout, le programme se termine, et la libération du mutex n'a pas eu d'importance).

7voto

supercat Points 25534

L'une des principales utilisations d'un mutex est d'assurer que le seul code qui verra un objet partagé dans un état qui ne satisfait pas ses invariants est le code qui a mis (temporairement, espérons-le) l'objet dans cet état. Un modèle normal pour le code qui a besoin de modifier un objet est :

  1. Acquérir le mutex
  2. Apporter à l'objet des modifications qui rendent son état invalide.
  3. Apporter à l'objet des modifications qui font que son état redevient valide.
  4. Libérer le mutex

Si quelque chose ne va pas après que le n°2 ait commencé et avant que le n°3 ait fini, l'objet peut être laissé dans un état qui ne satisfait pas ses invariants. Puisque le modèle approprié est de libérer un mutex avant de le disposer, le fait que le code dispose d'un mutex sans le libérer implique que quelque chose a mal tourné quelque part. En tant que tel, il peut être dangereux pour le code d'entrer dans le mutex (puisqu'il n'a pas été libéré), mais il n'y a aucune raison d'attendre que le mutex soit libéré (puisque, ayant été disposé, il ne le sera jamais). Ainsi, la bonne marche à suivre est de lancer une exception.

Un modèle un peu plus agréable que celui mis en œuvre par l'objet mutex de .NET consiste à faire en sorte que la méthode " acquire " renvoie un objet IDisposable qui encapsule non pas le mutex, mais plutôt une acquisition particulière de celui-ci. La disposition de cet objet libère alors le mutex. Le code peut alors ressembler à quelque chose comme :

using(acq = myMutex.Acquire())
{
   ... stuff that examines but doesn't modify the guarded resource
   acq.EnterDanger();
   ... actions which might invalidate the guarded resource
   ... actions which make it valid again
   acq.LeaveDanger();
   ... possibly more stuff that examines but doesn't modify the resource
}

Si le code interne échoue entre EnterDanger y LeaveDanger l'objet d'acquisition doit alors invalider le mutex en appelant Dispose sur elle, car la ressource gardée peut être dans un état corrompu. Si le code interne échoue ailleurs, le mutex doit être libéré puisque la ressource gardée est dans un état valide, et le code à l'intérieur de l'objet using n'aura plus besoin d'y accéder. Je n'ai pas de recommandations particulières de bibliothèques mettant en œuvre ce modèle, mais il n'est pas particulièrement difficile à mettre en œuvre en tant qu'enveloppe autour d'autres types de mutex.

4voto

Ian Ringrose Points 19115

Nous devons comprendre plus que .net pour savoir ce qui se passe. Le début de la page MSDN donne le premier indice que quelque chose de "bizarre" se passe :

Une primitive de synchronisation qui peut également être utilisée pour interprocessus la synchronisation.

Un Mutex est un Win32 " Objet nommé "chaque processus le verrouille par nom L'objet .net est juste une enveloppe autour des appels Win32. Le Muxtex lui-même vit dans l'espace d'adresse du noyau Windows, et non dans l'espace d'adresse de votre application.

Dans la plupart des cas, il est préférable d'utiliser un Moniteur si vous essayez seulement de synchroniser l'accès aux objets au sein d'un seul processus.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X