2009-02-26 8 views
67

In un'app di threading C#, se dovessi bloccare un oggetto, diciamo una coda, e se si verifica un'eccezione, l'oggetto rimarrà bloccato? Ecco il pseudo-codice:Un oggetto bloccato rimane bloccato se si verifica un'eccezione al suo interno?

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

Da quanto ho capito, il codice dopo la cattura non esegue - ma mi sono chiesto se il blocco sarà liberato.

+1

Come un pensiero finale (vedi aggiornamenti) - probabilmente dovresti tenere il blocco solo per la durata della rimozione della coda ... eseguire l'elaborazione ** all'esterno ** della serratura. –

+1

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

+0

Grazie, devo averlo perso, dovrei cancellare questa domanda? – Vort3x

risposta

70

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; primo, lock è essenzialmente:

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

Secondo; catturi e non rilancia l'eccezione interna, quindi lo lock non vede mai effettivamente un'eccezione. Ovviamente, stai tenendo il blocco per tutta la durata di un MessageBox, il che potrebbe essere un problema.

Quindi verrà rilasciato in tutte le eccezioni irreversibili più catastrofiche ma fatali.

+11

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 costringerà la cattura e ti fa comodo. – Khadaji

+10

Come gettare nuova Eccezione ("a scopo illustrativo"); ;-p –

+1

Tranne se si verifica un 'TheadAbortException' tra' Monitor.Enter' e 'try': http: //blogs.msdn.com/ericlippert/archive/2009/03/06/locks-and-exceptions-do-not-mix.aspx –

4

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

Situazioni come questa sono la vera ragione dell'esistenza della parola chiave lock. Aiuta gli sviluppatori a verificare che il blocco venga rilasciato nel blocco finally.

Se si è obbligati a utilizzare Monitor.Enter/Exit ad es. per supportare un timeout, è necessario assicurarsi di effettuare la chiamata a Monitor.Exit nel blocco finally per garantire il corretto rilascio del blocco in caso di eccezione.

13

"Una dichiarazione di blocco viene compilato per una chiamata a Monitor.Enter, e poi un blocco try ... finally. Nel blocco finally, Monitor.Exit si chiama.

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

Tratto da: This site

+0

C'è almeno un caso in cui questo non è vero: abortire il thread in modalità debug su versioni .net precedenti 4. Il motivo è che il compilatore C# inserisce un NOP tra 'Monitor.Enter' e' try', quindi che la condizione "immediatamente segue" della JIT viene violata. – CodesInChaos

36

sì, che rilascerà correttamente; lock funge da try/finally, con lo Monitor.Exit(myLock) infine, quindi non importa come si esce verrà rilasciato. Come nota a margine, è meglio evitare catch(... e) {throw e;}, in quanto ciò danneggia la traccia dello stack su e; è meglio non prenderlo a tutti, o in alternativa: utilizzare throw; anziché throw e; che esegue un nuovo lancio.

Se si vuole veramente sapere, un blocco in C# 4/.NET 4 è:

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

Il blocco sarà rilasciato in modo corretto. A lock atti come questo:

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

E finally blocchi sono garantiti da eseguire, non importa come si lascia il blocco try.

+0

In realtà, ** no ** codice è "garantito" da eseguire (ad esempio, è possibile estrarre il cavo di alimentazione), e * non è esattamente * come appare un lucchetto in 4.0 - [vedi qui] (http://blogs.msdn.com/b/ericlippert/archive/2009/03/06/locks-and-exceptions-do-not-mix.aspx) –

+1

@MarcGravell: Ho pensato di mettere due note a piè di pagina su questi due punti esatti. E poi ho pensato che non avrebbe importato molto :) – Ryan

+1

@MarcGravel: Penso che tutti pensino che uno non stia parlando di una situazione di "staccare la spina", dato che non è qualcosa che un programmatore ha sotto controllo :) – Vort3x

83

Ho notato che nessuno ha menzionato nelle loro risposte a questa vecchia domanda che rilasciare un blocco su un'eccezione è una cosa incredibilmente pericolosa da fare. Sì, le istruzioni di blocco in C# hanno semantica "definitiva"; quando il controllo esce normalmente o in modo anomalo, il blocco viene rilasciato. Parli tutti di questo come se fosse una buona cosa, ma è una brutta cosa! La cosa giusta da fare se si dispone di un'area bloccata che genera un'eccezione non gestita è terminare il processo malato immediatamente prima di distruggere altri dati utente, non liberare il blocco e continuare a.

Guardalo in questo modo: immagina di avere un bagno con un lucchetto sulla porta e una fila di persone che aspettano fuori. Una bomba in bagno si spegne uccidendo la persona lì dentro. La tua domanda è "in quella situazione la serratura verrà automaticamente sbloccata in modo che la persona successiva possa entrare in bagno?" Si lo farà. Questa non è una buona cosa. Una bomba è appena esplosa e ha ucciso qualcuno! L'impianto idraulico è probabilmente distrutto, la casa non è più strutturalmente sana e ci potrebbe essere un'altra bomba lì. La cosa giusta da fare è far uscire tutti il ​​più velocemente possibile e demolire l'intera casa.

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

Se si è bloccata una regione di codice per eseguire una mutazione senza un altro thread che legge lo stato contemporaneamente, e la mutazione genera, quindi se i dati non erano corrotti prima, è sicuro che ora è. Quale è esattamente lo scenario che il blocco dovrebbe proteggere contro. Ora il codice che è in attesa di leggere quello stato sarà immediatamente avere accesso allo stato corrotto e probabilmente si bloccherà. Ancora una volta, la cosa giusta da fare è terminare il processo.

Non importa come lo si taglia, un'eccezione all'interno di un blocco è cattive notizie. La domanda giusta da porre non è "la mia serratura verrà ripulita in caso di eccezione?" La domanda giusta da porsi è "come faccio a garantire che non ci sia mai un'eccezione all'interno di un lucchetto? E se c'è, allora come posso strutturare il mio programma in modo che le mutazioni vengano riportate ai precedenti stati positivi?"

+12

è piuttosto ortogonale al blocco dell'IMO. Se ottieni un'eccezione prevista, vuoi pulire tutto, compresi i lucchetti. E se ricevi un'eccezione imprevista, hai un problema, con o senza serrature. – CodesInChaos

+8

Penso che la situazione sopra descritta sia un generalismo. A volte le eccezioni descrivono eventi catastrofici. A volte no. Ognuno li usa in modo diverso nel codice. È perfettamente valido che un'eccezione sia un segnale di un evento eccezionale ma non catastrofico: supponendo eccezioni = catastrofico, il caso di chiusura del processo è troppo specifico. Il fatto che possa essere un evento catastrofico non rimuove dalla validità della domanda - lo stesso ragionamento potrebbe portarti a non gestire mai alcuna eccezione, nel qual caso il processo verrà chiuso ... –

+1

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

Problemi correlati