2012-11-29 4 views
6

Oggi per il problema di errore di rilevamento, ho letto l'errore erlang's e la gestione degli errori dei documenti.quando utilizzare throw/1 vs. exit/1 vs. error/1 in Erlang?

Per il tipo di errore generato, esistono 2 tipi di classe, uno è exit e l'altro è throw. Dopo aver scaricato il codice sorgente, l'espressione throw and exit si è già ammassata.

Entrambi sono simili, e sembra che le espressioni di cattura siano diverse.

([email protected])30> catch throw ({aaa}). 
{aaa} 
([email protected])31> catch exit ({aaa}). 
{'EXIT',{aaa}} 
([email protected])32> catch gen_server:call(aaa,{aaa}). 
{'EXIT',{noproc,{gen_server,call,[aaa,{aaa}]}}} 

Potrebbe dirmi quando utilizzare tiro e quando utilizzare l'uscita?

risposta

12

ci sono classi che possono essere catturati con una try ... catch: throw, error e exit.

  • throw viene generato utilizzando throw/1 ed è destinato ad essere utilizzato per rendimenti non locali e non genera un errore a meno che non e 'colto (quando si ottiene un errore di nocatch).

  • error viene generato quando il sistema rileva un errore. È possibile generare un errore in modo esplicito utilizzando error/1. Il sistema include anche uno stacktrace nel valore di errore generato, ad esempio {badarg,[...]}.

  • exit viene generato utilizzando exit/1 ed è destinato a segnalare che questo processo è di morire.

La differenza tra error/1 e exit/1 che non è grande, più circa l'intenzione che l'stacktrace generato da errori aumenta.

La differenza tra loro è in realtà più evidente quando si fa catch ...: Quando si utilizza throw/1 poi il catch solo restituisce il valore gettato, come ci si aspetta da un ritorno non locale; quando un error/1 viene utilizzato quindi i rendimenti catch{'EXIT',Reason} dove Reason contiene lo stacktrace; mentre da exit/1catch restituisce anche {'EXIT',Reason} ma Reason contiene un'unica ragione uscita effettiva. try ... catch sembra che li equipaggi, ma sono/erano molto diversi.

+0

Grazie mille per il vostro aiuto. Adesso è più chiaro. –

+0

@ChenYu - mi scuso per la mia spiegazione sciatta. rvirding: grazie per averlo chiarito. Permettetemi di risolvere/cancellare la mia risposta - minore confusione, meglio è! – Faiz

6

[AGGIORNATO]

ho sorvolato l'importante differenza tra tiro e errore, ha sottolineato da Robert Virding. Questa modifica è solo per la cronaca!

tiroerror deve essere utilizzato in cui si potrebbe usare throw in altre lingue. Un errore in un processo in esecuzione è stato rilevato dal codice, che segnala un'eccezione con error/1. Lo stesso processo lo cattura (probabilmente più in alto nello stack) e l'errore deve essere gestito all'interno dello stesso processo. error porta sempre con sé uno stacktrace.

throw deve essere utilizzato per non segnalare un errore, ma solo per restituire un valore da una funzione profondamente annidata. Poiché si svolge lo stack, chiamando throw restituisce il valore generato nel punto in cui è stato catturato. Come nel caso di error, stiamo cattura roba che è stato gettato, solo ciò che è stato gettato non era un errore, ma piuttosto solo un valore passato lo stack. Questo è il motivo per cui il lancio non porta con sé uno stacktrace.

Come esempio forzato, se volessimo implementare una funzione exists per gli elenchi, (simile a quello che list:any lo fa) e come un esercizio senza senza fare il recursing noi stessi, e utilizzando solo list:foreach, quindi throw potrebbe essere utilizzato qui:

exists(P, List) -> 
    F = fun(X) -> 
    case P(X) of 
     true -> throw(true); 
     Whatever -> Whatever 
    end 
    end, 
    try lists:foreach(F, List) of 
    ok -> false 
    catch 
    true -> true 
    end. 

un valore gettata ma non pescato è trattato come un error: un'eccezione nocatch verrà generato.

EXIT deve essere segnalato da un processo quando 'si arrende'. Il genitore processo gestisce l'uscita, mentre il processo bambino muore solo. Questa è la filosofia di Erlang Let-it-crash.

Così EXIT exit/1 's non è quello di essere catturati all'interno dello stesso processo, ma a sinistra per il genitore. errori error/1 s' sono locali al processo - cioè una questione di ciò che accade e come viene gestita dal processo stesso; throw/1 viene utilizzato per il controllo del flusso attraverso lo stack.

[UPDATE]

  1. Questo tutorial spiega bene: http://learnyousomeerlang.com/errors-and-exceptions
  2. Nota c'è anche un exit/2 - chiamato con un Pid di un processo per inviare l'EXIT per. exit/1 implica il processo genitore.
+0

Grazie mille per la vostra informazione. –

+0

@ChenYu Se leggi il capitolo vedrai che la spiegazione qui è sbagliata. Tu usi 'throw/1' per fare un ritorno non locale, ** NOT ** per lanciare un errore, usi' error/1' per segnalare un errore e tu esci/1' per mostrare che il processo è morire. Ci sono ** 3 ** tipi di classi: 'throw',' error' e 'exit' – rvirding

+0

@rvirding spero che abbia fatto ammenda con questo. Feedback apprezzato! – Faiz

0

Sono nuovo di Erlang, ma ecco come penso a cosa sono queste cose, le loro differenze, quello che stanno utilizzati per, ecc .:

throw: una condizione che deve essere maneggiato a livello locale (ad esempio all'interno del processo corrente). Per esempio. il chiamante sta cercando un elemento in una raccolta, ma non sa se la raccolta contiene effettivamente un tale elemento; quindi, il destinatario potrebbe lanciare se tale elemento non è presente e il chiamante rilevare l'assenza utilizzando try[/of]/catch. Se chiamante trascura di fare questo, allora questo viene trasformato in un nocatcherror (spiegato qui di seguito).

exit: L'attuale processo è fatto. Per esempio. ha semplicemente finito (in quel caso, ci si passa normal, che è considerato il uguale alla funzione originale di ritorno), o il suo funzionamento è stato annullato (per esempio normalmente loop a tempo indeterminato, ma ha appena ricevuto un messaggio shut_down).

error: il processo ha fatto qualcosa e/o ha raggiunto uno stato che il programmatore non ha tenuto in considerazione (ad esempio 1/0), ritiene impossibile (ad esempio case ... of riscontra un valore che non corrisponde ad alcun caso), oppure non è soddisfatta una condizione preliminare (ad esempio, l'input non è vuoto). In questo caso, il recupero locale non ha senso. Pertanto, né lo throw né lo exit sono appropriati. Poiché ciò è inaspettato, una traccia dello stack fa parte del Motivo.

Come si può vedere, l'elenco è in ordine crescente:

throw è per condizioni sane che il chiamante si aspetta da gestire. Cioè la gestione avviene entro il il processo corrente.

exit è anche sano, ma dovrebbe terminare il processo corrente semplicemente perché il processo è terminato.

error è folle. È successo qualcosa che non può essere ragionevolmente recuperato da (di solito un bug?), E il ripristino locale non sarebbe appropriato.


vs. altre lingue:

throw è analogo al modo in cui le eccezioni controllate sono utilizzate in Java. Considerando che, error viene utilizzato in un modo più analogo alle eccezioni non controllate. Le eccezioni controllate sono eccezioni che il chiamante deve gestire. Java richiede di eseguire il wrap delle chiamate in try/catch o dichiarare che il metodo è throws tali eccezioni. Mentre le eccezioni non controllate si propagano generalmente al chiamante più esterno.

exit non ha un buon analogico in più lingue "convenzionali" come Java, C++, Python, JavaScript, Ruby, ecc exit vagamente come un uber- return: invece di restituire alla fine, si può tornare dal al centro di una funzione, eccetto che non si ritorna solo dalla funzione corrente, si ritorna da tutti ALL.


exit Esempio

serve_good_times() -> 
    receive 
    {top_of_the_mornin, Sender} -> 
     Sender ! and_the_rest_of_the_day_to_yourself; 
    {you_suck, Sender} -> 
     Sender ! take_a_chill_pill; 
    % More cases... 

    shut_down -> 
     exit(normal) 
    end, 
    serve_good_times() 
end 

Dal serve_good_times chiamate se stesso dopo quasi tutti i messaggi, il programmatore ha deciso che non vogliamo ripetere quella chiamata in ogni caso ricevere. Pertanto, ha effettuato la chiamata dopo il alla ricezione. Ma allora, che succede se serve_good_times decide di smettere di chiamarsi? Questo è dove exit viene in soccorso. Passando da normal a exit, il processo termina come se fosse stata restituita l'ultima chiamata di funzione.

Come tale, è in genere inappropriato chiamare exit in una libreria generica, ad esempio lists. Nessuna delle attività della biblioteca è se il processo debba terminare; che dovrebbe essere deciso dal codice dell'applicazione.


Informazioni su Anormale exit?

Questo è importante se un altro processo (il processo "a distanza") è legata al processo di "locale" che chiama exit (e process_flag(trap_exit, true) non è stato chiamato): Proprio come l'ultima funzione che restituisce, exit(normal) non causa processo remoto per uscire . Ma se il processo locale effettua una chiamata exit(herp_derp), anche il processo remoto viene chiuso con Reason=herp_derp. Naturalmente, se il processo remoto è collegato a più processi, ottengono anche il segnale di uscita con Reason=herp_derp. Pertanto, le uscite non normal provocano una reazione a catena.

Diamo uno sguardo a questo in azione:

1> self(). 
<0.32.0> 
2> spawn_link(fun() -> exit(normal) end). 
<0.35.0> 
3> self(). 
<0.32.0> 
4> 
4> 
4> spawn_link(fun() -> exit(abnormal) end). 
** exception exit: abnormal 
5> self(). 
<0.39.0> 
6> 

Il primo processo che abbiamo procreati non ha causato il guscio per uscire (possiamo dire, perché self restituito lo stesso PID prima e dopo spawn_link). MA il secondo processo ha fatto uscire la shell (e il sistema ha sostituito il processo shell con uno nuovo).

Ovviamente, se il processo remoto utilizza process_flag(trap_exit, true), viene visualizzato solo un messaggio, indipendentemente dal fatto che il processo locale passi normal o altro al numero exit. L'impostazione di questo flag interrompe la reazione a catena.

6> process_flag(trap_exit, true). 
false 
7> spawn_link(fun() -> exit(normal) end). 
<0.43.0> 
8> self(). 
<0.39.0> 
9> flush(). 
Shell got {'EXIT',<0.43.0>,normal} 
ok 
10>           
10> 
10> spawn_link(fun() -> exit(abnormal) end). 
<0.47.0> 
11> self(). 
<0.39.0> 
12> flush(). 
Shell got {'EXIT',<0.47.0>,abnormal} 

Ricordiamo che ho detto che exit(normal) viene trattato come la funzione originale di ritorno:

13> spawn_link(fun() -> ok end). 
<0.51.0> 
14> flush(). 
Shell got {'EXIT',<0.51.0>,normal} 
ok 
15> self(). 
<0.39.0> 

che ne sai: la stessa cosa è successa come quando exit(normal) è stato chiamato. Meraviglioso!

+0

btw, exit (self(), wat) NON equivale a exit (wat). I due si comportano diversamente quando process_flag (trap_exit, true) è in uso (dal thread corrente): il primo risulta in un messaggio 'EXIT'; mentre, quest'ultimo davvero esce proprio lì e poi. – allyourcode

+0

Anche se si esce (NotSelf, reason) e NotSelf sta usando trap_exit, allora ottiene un messaggio piuttosto che uscire, proprio come se un remoto (dal punto di vista di NotSelf) fosse uscito. Per me, questo è davvero non intuitivo; Pensavo che trap_exit riguardasse solo i processi remoti che si stavano esaurendo, ma si applica anche a se stesso quando si esce usando exit/2, anche se chiamato da self()! Il mio cervello (e le mie sensazioni) fa male adesso ... – allyourcode

+0

Un'altra cosa che non mi ero reso conto dei messaggi {'ESCI', Pid, ​​Motivo} quando sono generati da exit/2: Pid è il processo che chiama exit/2 , non il processo nominato dal primo argomento di exit/2. Crazy ... – allyourcode