Un oggetto bloccato rimane bloccato se si verifica un'eccezione al suo interno?


87

In ac # threading app, se dovessi bloccare un oggetto, diciamo una coda e se si verifica un'eccezione, l'oggetto rimarrà bloccato? Ecco lo pseudo-codice:

int ii;
lock(MyQueue)
{
   MyClass LclClass = (MyClass)MyQueue.Dequeue();
   try
   {
      ii = int.parse(LclClass.SomeString);
   }
   catch
   {
     MessageBox.Show("Error parsing string");
   }
}

A quanto ho capito, il codice dopo il fermo non viene eseguito, ma mi chiedevo se il blocco verrà liberato.


1
Come pensiero finale (vedi aggiornamenti) - probabilmente dovresti tenere il blocco solo per la durata dell'annullamento della coda ... eseguire l'elaborazione al di fuori del blocco.
Marc Gravell

1
Il codice dopo la cattura viene eseguito perché viene gestita l'eccezione
cjk

Grazie devo essermi perso quello, devo eliminare questa domanda?
Vort3x,

4
Sembra che il codice di esempio non sia adatto a questa domanda, ma la domanda è abbastanza valida.
SalvadorGomez

Di C # Designer - Blocco ed eccezione
Jivan

Risposte:


91

Primo; hai considerato TryParse?

in li;
if(int.TryParse(LclClass.SomeString, out li)) {
    // li is now assigned
} else {
    // input string is dodgy
}

Il blocco verrà rilasciato per 2 motivi; in primo luogo, lockè essenzialmente:

Monitor.Enter(lockObj);
try {
  // ...
} finally {
    Monitor.Exit(lockObj);
}

Secondo; catturi e non rilanci l'eccezione interna, quindi locknon vede mai effettivamente un'eccezione. Naturalmente, stai tenendo premuto il lucchetto per la durata di un MessageBox, il che potrebbe essere un problema.

Quindi verrà rilasciato in tutte le eccezioni irrecuperabili tranne le più fatali catastrofiche.


15
Sono a conoscenza del tryparse, ma non è veramente rilevante per la mia domanda. Questo era un codice semplice per spiegare la domanda, non una vera preoccupazione per quanto riguarda l'analisi. Si prega di sostituire l'analisi con qualsiasi codice che forzerà il blocco e ti metterà a tuo agio.
Khadaji,

13
Che ne dici di lanciare una nuova eccezione ("a scopo illustrativo"); ;-p
Marc Gravell

1
Tranne se si TheadAbortExceptionverifica un errore tra Monitor.Entere try: blogs.msdn.com/ericlippert/archive/2009/03/06/…
Igal Tabachnik

2
"eccezioni irreversibili catastrofiche fatali" come attraversare i torrenti.
Phil Cooper

99

Noto che nessuno ha menzionato nelle risposte a questa vecchia domanda che rilasciare un blocco su un'eccezione è una cosa incredibilmente pericolosa da fare. Sì, le istruzioni lock in C # hanno la semantica "finalmente"; quando il controllo esce dalla serratura normalmente o in modo anomalo, la serratura viene rilasciata. Parlate tutti di questo come se fosse una buona cosa, ma è una brutta cosa! La cosa giusta da fare se si dispone di una regione bloccata che genera un'eccezione non gestita è terminare il processo malato immediatamente prima che distrugga più dati utente , non liberare il blocco e andare avanti .

Guardala in questo modo: supponi di avere un bagno con una serratura alla porta e una fila di persone che aspettano fuori. Una bomba nel bagno esplode uccidendo la persona lì dentro. La tua domanda è "in quella situazione il lucchetto verrà automaticamente sbloccato in modo che la prossima persona possa entrare in bagno?" Si lo farà. Non è una buona cosa. Una bomba è appena esplosa lì dentro e ha ucciso qualcuno! Probabilmente l'impianto idraulico è stato distrutto, la casa non è più strutturalmente solida e potrebbe esserci un'altra bomba . La cosa giusta da fare è far uscire tutti il ​​prima possibile e demolire l'intera casa.

Voglio dire, pensaci bene: se hai bloccato una regione di codice per leggere da una struttura dati senza che venisse mutata su un altro thread, e qualcosa in quella struttura dati ha generato un'eccezione, le probabilità sono buone che sia perché la struttura dati è corrotto . I dati dell'utente sono ora incasinati; non vuoi provare a salvare i dati utente a questo punto perché stai salvando dati corrotti . Basta terminare il processo.

Se hai bloccato una regione di codice per eseguire una mutazione senza che un altro thread leggesse lo stato nello stesso momento e la mutazione genera, allora se i dati non erano corrotti prima, sicuramente lo è ora . Che è esattamente lo scenario da cui la serratura dovrebbe proteggersi . Ora il codice in attesa di leggere quello stato avrà immediatamente accesso allo stato danneggiato e probabilmente si bloccherà. Ancora una volta, la cosa giusta da fare è terminare il processo.

Non importa come lo tagli, un'eccezione all'interno di una serratura è una cattiva notizia . La domanda giusta da fare non è "la mia serratura verrà pulita in caso di eccezione?" La domanda giusta da porsi è "come posso assicurarmi che non ci sia mai un'eccezione all'interno di una serratura? E se c'è, allora come strutturo il mio programma in modo che le mutazioni vengano riportate agli stati buoni precedenti?"


19
Questo problema è abbastanza ortogonale al blocco dell'IMO. Se ottieni un'eccezione prevista, vuoi ripulire tutto, compresi i blocchi. E se ricevi un'eccezione imprevista, hai un problema, con o senza blocchi.
CodesInChaos

10
Penso che la situazione sopra descritta sia un generalismo. A volte le eccezioni descrivono eventi catastrofici. A volte non lo fanno. Ognuno li usa in modo diverso nel codice. È perfettamente valido che un'eccezione sia un segnale di un evento eccezionale ma non catastrofico, supponendo che eccezioni = catastrofico, il caso che termina il processo sia troppo specifico. Il fatto che possa trattarsi di un evento catastrofico non toglie nulla alla validità della domanda: la stessa linea di pensiero potrebbe portarti a non gestire mai alcuna eccezione, nel qual caso il processo verrà chiuso ...
Gerasimos R

1
@ GerasimosR: Infatti. Due punti da sottolineare. In primo luogo, si dovrebbe presumere che le eccezioni siano catastrofiche fino a quando non si determini che siano benigne. In secondo luogo, se si ottiene un'eccezione benigna generata da una regione bloccata, la regione bloccata è probabilmente progettata male; probabilmente sta facendo troppo lavoro all'interno della serratura.
Eric Lippert

1
Con tutto il rispetto, ma sembra che lei stia dicendo, signor Lippert, che il design C # del blocco di blocco che sotto il cofano impiega un'istruzione finalmente è una cosa negativa. Invece, dovremmo bloccare completamente ogni app che abbia mai un'eccezione in un'istruzione di blocco che non è stata catturata all'interno del blocco. Forse non è quello che stai dicendo, ma se è così, sono davvero felice che la squadra abbia seguito la strada che hanno fatto e non la tua (estremista per ragioni puriste!). Con tutto il rispetto.
Nicholas Petersen

2
Nel caso non sia chiaro cosa intendo per non componibile: supponiamo di avere un metodo "trasferimento" che prende due liste, se d, blocca s, blocca d, rimuove un elemento da s, aggiunge l'elemento a d, sblocca d, sblocca s. Il metodo è corretto solo se nessuno ha mai tentativi di trasferimento dalla lista X alla lista Y al tempo stesso come qualcun altro tentativi di trasferimento da Y a X . La correttezza del metodo di trasferimento non consente di costruire una soluzione corretta a un problema più ampio da esso, perché i blocchi sono mutazioni pericolose dello stato globale. Per "trasferire" in modo sicuro è necessario conoscere ogni blocco del programma.
Eric Lippert

43

sì, verrà rilasciato correttamente; lockagisce come try/ finally, con il Monitor.Exit(myLock)infine, quindi non importa come esci, verrà rilasciato. Come nota a margine, catch(... e) {throw e;}è meglio evitare, in quanto ciò danneggia la traccia dello stack e; è meglio non prenderlo affatto , o in alternativa: usa throw;piuttosto throw e;che fare un rilancio.

Se vuoi davvero saperlo, un blocco in C # 4 / .NET 4 è:

{
    bool haveLock = false;
    try {
       Monitor.Enter(myLock, ref haveLock);
    } finally {
       if(haveLock) Monitor.Exit(myLock);
    }
} 

14

"Un'istruzione lock viene compilata in una chiamata a Monitor.Enter, e quindi un blocco try ... finalmente. Nel blocco finalmente, viene chiamato Monitor.Exit.

La generazione del codice JIT sia per x86 che per x64 garantisce che non possa verificarsi un'interruzione del thread tra una chiamata Monitor.Enter e un blocco try che la segue immediatamente. "

Tratto da: Questo sito


1
C'è almeno un caso in cui ciò non è vero: aborto del thread in modalità di debug nelle versioni .net precedenti alla 4. Il motivo è che il compilatore C # inserisce un NOP tra Monitor.Entere e try, in modo che la condizione "segue immediatamente" del JIT è stato violato.
CodesInChaos

6

Il tuo blocco verrà rilasciato correttamente. A si lockcomporta in questo modo:

try {
    Monitor.Enter(myLock);
    // ...
} finally {
    Monitor.Exit(myLock);
}

E l' finallyesecuzione dei blocchi è garantita, indipendentemente da come esci dal tryblocco.


In realtà, nessun codice è "garantito" per l'esecuzione (potresti tirare il cavo di alimentazione, per esempio), e questo non è esattamente come appare un lucchetto in 4.0 - vedi qui
Marc Gravell

1
@ MarcGravell: ho pensato di mettere due note a piè di pagina su questi due punti esatti. E poi ho pensato che non avrebbe avuto molta importanza :)
Ry-

1
@ MarcGravel: Penso che tutti presumano sempre che non si stia parlando di una situazione di "staccare la spina", poiché non è qualcosa su cui un programmatore ha alcun controllo :)
Vort3x

5

Giusto per aggiungere un po 'all'eccellente risposta di Marc.

Situazioni come questa sono la vera ragione per l'esistenza della lockparola chiave. Aiuta gli sviluppatori ad assicurarsi che il blocco sia rilasciato nel finallyblocco.

Se sei costretto a usare Monitor.Enter/ Exiteg per supportare un timeout, devi assicurarti di inserire la chiamata a Monitor.Exitnel finallyblocco per garantire il corretto rilascio del blocco in caso di eccezione.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.