Waarom wordt Mutex niet vrijgegeven als het wordt weggegooid?

Ik heb de volgende code:

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
    }
}

Ik heb een onderbrekingspunt ingesteld in het if-blok en dezelfde code uitgevoerd in een andere instantie van Visual Studio. Zoals verwacht blokkeert de .WaitOneoproep. Tot mijn verbazing krijg ik echter, zodra ik in eerste instantie doorgaen het blok usingstopt, in het tweede proces een uitzondering over een verlaten Mutex.

De oplossing is om ReleaseMutexaan te roepen:

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();
}

Nu werken de dingen zoals verwacht.

Mijn vraag:Meestal is het punt van een IDisposabledat het opruimtin welke staat je dingen ook plaatst. Ik zou kunnen zien dat er meerdere wachten releasesbinnen een using-blok, maar als het handvat naar de Mutex wordt verwijderd, moet het dan niet automatisch worden vrijgegeven? Met andere woorden, waarom moet ik ReleaseMutexaanroepen als ik in een using-blok zit?

Ik maak me nu ook zorgen dat als de code in het if-blok crasht, ik de mutexen laat rondslingeren.

Heeft het enig voordeel om Mutexin een using-blok te plaatsen? Of moet ik gewoon een Mutex-instantie nieuwmaken, deze in een try/catch verpakken en ReleaseMutex()aanroepen in de eindelijkblok (in feite precies implementeren wat ik dachtDispose()zou doen)


Antwoord 1, autoriteit 100%

De documentatielegt (in de sectie “Opmerkingen”) dat er een conceptueel verschil is tussen het instantierenvan een Mutex-object (wat nietin feite iets speciaals doet wat betreft synchronisatie) en het verwervenvan een Mutex (met behulp van WaitOne). Let op:

  • WaitOneretourneert een boolean, wat betekent dat het verkrijgen van een Mutex kan mislukken(time-out) en beide gevallen moeten worden afgehandeld
  • Als WaitOnetrueretourneert, heeft de aanroepende thread de Mutex verworven en moetReleaseMutexaanroepen, of anders wordt de Mutex verlaten
  • Als het falseretourneert, mag de aanroepende thread nietReleaseMutex
  • aanroepen

Dus er is meer bij Mutexen dan alleen instantiatie. Wat betreft de vraag of u toch usingmoet gebruiken, laten we eens kijken naar wat Disposedoet (zoals overgenomen van WaitHandle):

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

Zoals we kunnen zien, is de Mutex nietvrijgegeven, maar er komt wat opruiming bij kijken, dus vasthouden aan usingzou een goede aanpak zijn.

Over hoe u verder moet gaan, kunt u natuurlijk een try/finally-blok gebruiken om ervoor te zorgen dat, als de Mutex wordt verkregen, deze correct wordt vrijgegeven. Dit is waarschijnlijk de meest directe aanpak.

Als het je echtniets kan schelen dat de Mutex niet wordt verkregen (wat je niet hebt aangegeven, aangezien je een TimeSpandoorgeeft aan WaitOne), je zou Mutexin je eigen klasse kunnen inpakken die IDisposableimplementeert, de Mutex in de constructor verkrijgen (met behulp van WaitOne()zonder argumenten), en laat het los in Dispose. Hoewel, ik zou dit waarschijnlijk niet aanraden, omdat dit ervoor zou zorgen dat je threads voor onbepaalde tijd zouden wachten als er iets misgaat, en hoe dan ook, er zijn goede redenen voor het expliciet behandelen van beide gevallen bij een acquisitiepoging, zoals vermeld door @HansPassant.


Antwoord 2, autoriteit 62%

Deze ontwerpbeslissing is lang, lang geleden genomen. Meer dan 21 jaar geleden, ruim voordat .NET ooit werd bedacht of de semantiek van IDisposable ooit werd overwogen. De .NET Mutex-klasse is een wrapper-klasse voor de onderliggende besturingssysteemondersteuning voor mutexen. De constructor pinvoelt CreateMutex, de WaitOne( ) methode pinvokes WaitForSingleObject().

Let op de WAIT_ABANDONED-retourwaarde van WaitForSingleObject(), dat is degene die de uitzondering genereert.

De Windows-ontwerpers hebben de keiharde regel ingevoerd dat een thread die eigenaar is van de mutex moetReleaseMutex() aanroepen voordat deze wordt afgesloten. En als dat niet het geval is, is dit een zeer sterke aanwijzing dat de thread op een onverwachte manier is beëindigd, meestal via een uitzondering. Wat inhoudt dat de synchronisatie verloren gaat, een zeer ernstige threading-bug. Vergelijk met Thread.Abort(), een zeer gevaarlijke manier om een ​​thread in .NET om dezelfde reden te beëindigen.

De .NET-ontwerpers hebben dit gedrag op geen enkele manier veranderd. Niet in de laatste plaats omdat er geen andere manier is om de toestand van de mutex te testen dan door te wachten. U moetReleaseMutex() aanroepen. En merk op dat uw tweede fragment ook niet correct is; je kunt het niet noemen op een mutex die je niet hebt aangeschaft. Het moet in de hoofdtekst van het if()-statement worden geplaatst.


Antwoord 3, autoriteit 13%

Ok, ik plaats een antwoord op mijn eigen vraag. Voor zover ik kan zien, is ditde ideale manier om een ​​Mutexte implementeren die:

  1. Wordt altijd weggegooid
  2. Wordt vrijgegeven als WaitOnesuccesvol was.
  3. Wordt niet verlaten als een code een uitzondering genereert.

Hopelijk helpt dit iemand!

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();
        }
    }
}

Update:sommigen beweren dat als de code in het try-blok je bron in een onstabiele staat brengt, je nietde Mutex en laat het in plaats daarvan verlaten worden. Met andere woorden, roep gewoon mut.ReleaseMutex();aan wanneer de code succesvol is voltooid, en plaats deze niet in het finally-blok. De code die de Mutex verkrijgt, kan deze uitzondering dan opvangen en het juiste doen.

In mijn situatie verander ik niet echt een staat. Ik gebruik tijdelijk een TCP-poort en kan niet tegelijkertijd een ander exemplaar van het programma laten draaien. Om deze reden denk ik dat mijn oplossing hierboven prima is, maar die van jou kan anders zijn.


4, Autoriteit 6%

We moeten meer dan .NET begrijpen om te weten wat er aan de slag gaat van de MSDN-pagina geeft de eerste hint die iemand “ODD” aan de hand is:

Een synchronisatie-primitief die ook kan worden gebruikt voor interprocesses
synchronisatie.

Een mutex is een win32 genoemd object “, elk proces vergrendelt het op naam , het .NET-object is slechts een wikkelronde De Win32-oproepen. De Muxtex zelf leeft binnen de Windows Kernal-adresruimte, niet op uw applicatie-adresruimte.

In de meeste gevallen bent u beter af met behulp van een Monitor , als u alleen probeert de toegang tot objecten binnen één proces te synchroniseren.


5, Autoriteit 5%

Als u wilt garanderen dat de Mutex wordt vrijgegeven schakelaar op een poging vangst eindelijk blokkeren en de MUTEX-release in het uiteindelijke blok. Er wordt aangenomen dat je bezit en een handvat hebt voor de Mutex. Die logica moet worden opgenomen voordat de release wordt aangeroepen.


6, Autoriteit 3%

Houd er rekening mee: de MUTEX.DISPOSPOSE () Uitgevoerd door de Garbage Collector mislukt omdat het afvalverzamelingsproces niet de hendel volgens Windows bezit.


Antwoord 7, autoriteit 2%

Voor de laatste vraag.

Heeft het enig voordeel om Mutex in een gebruiksblok te plaatsen? Of, moet ik gewoon een Mutex-instantie vernieuwen, deze in een try/catch verpakken en ReleaseMutex() aanroepen in het laatste blok (in feite precies implementeren wat ik dacht dat Dispose() zou doen)

Als u het mutex-object niet weggooit, kan het maken van te veel mutex-objecten het volgende probleem opleveren.

---> (Inner Exception #4) System.IO.IOException: Not enough storage is available to process this command. : 'ABCDEFGHIJK'
 at System.Threading.Mutex.CreateMutexCore(Boolean initiallyOwned, String name, Boolean& createdNew)
 at NormalizationService.Controllers.PhysicalChunkingController.Store(Chunk chunk, Stream bytes) in /usr/local/...

Het programma gebruikt de genoemde mutex en wordt 200.000 keer uitgevoerd in de parallelle for-lus.
Door het gebruik van een instructie toe te voegen, wordt het probleem opgelost.

Other episodes