Que se passe-t-il si un bloc finalement lève une exception?

266

Si un bloc finalement lève une exception, que se passe-t-il exactement ?

Plus précisément, que se passe-t-il si l'exception est lancée à mi-chemin dans un bloc finally. Les autres instructions (après) de ce bloc sont-elles invoquées?

Je suis conscient que les exceptions se propageront vers le haut.

Jack Kada
la source
8
Pourquoi ne pas l'essayer? Mais sur ce genre de choses, celle que j'aime le plus est le retour avant le finalement et puis retourne quelque chose d'autre du bloc finalement. :)
ANeves
8
Toutes les instructions d'un bloc finally doivent s'exécuter. Il ne peut pas avoir de retour. msdn.microsoft.com/en-us/library/0hbbzekw(VS.80).aspx
Tim Scarborough

Réponses:

419

Si un bloc finalement lève une exception, que se passe-t-il exactement ?

Cette exception se propage de haut en bas et sera (peut) être gérée à un niveau supérieur.

Votre bloc finalement ne sera pas terminé au-delà du point où l'exception est levée.

Si le bloc finally s'exécutait lors du traitement d'une exception antérieure, cette première exception est perdue.

Spécification du langage C # 4 § 8.9.5: Si le bloc finally lève une autre exception, le traitement de l'exception actuelle est terminé.

Henk Holterman
la source
9
À moins que ce ne soit un ThreadAbortException, alors le bloc finalement entier sera terminé en premier, car il s'agit d'une section critique.
Dmytro Shevchenko
1
@Shedal - vous avez raison mais cela ne s'applique qu'à "certaines exceptions asynchrones", c'est-à-dire ThreadAbortException. Pour le code 1 thread normal, ma réponse est valable.
Henk Holterman
"La première exception est perdue" - c'est en fait très décevant, de manière occasionnelle, je trouve des objets IDisposable qui lèvent une exception dans Dispose (), ce qui entraîne la perte de l'exception à l'intérieur de la clause "using".
Alex Burtsev
"Je trouve des objets IDisposables qui lèvent une exception dans Dispose ()" - c'est bizarre pour le moins. Lire sur MSDN: ÉVITER de lever une exception depuis Dispose (bool) sauf sous ...
Henk Holterman
1
@HenkHolterman: Les erreurs de saturation de disque ne sont pas très courantes sur un disque dur principal directement connecté, mais les programmes écrivent parfois des fichiers sur des disques amovibles ou en réseau; les problèmes peuvent être beaucoup plus courants avec ceux-ci. Si quelqu'un tire une clé USB avant qu'un fichier ne soit entièrement écrit, il serait préférable de le lui dire immédiatement plutôt que d'attendre qu'il arrive où il va et trouve que le fichier est corrompu. Céder à l'erreur précédente lorsqu'il y en a une peut être un comportement sensé, mais lorsqu'il n'y a pas d'erreur antérieure, il serait préférable de signaler le problème plutôt que de le laisser non signalé.
supercat
101

Pour des questions comme celles-ci, j'ouvre généralement un projet d'application console vide dans Visual Studio et j'écris un petit exemple de programme:

using System;

class Program
{
    static void Main(string[] args)
    {
        try
        {
            try
            {
                throw new Exception("exception thrown from try block");
            }
            catch (Exception ex)
            {
                Console.WriteLine("Inner catch block handling {0}.", ex.Message);
                throw;
            }
            finally
            {
                Console.WriteLine("Inner finally block");
                throw new Exception("exception thrown from finally block");
                Console.WriteLine("This line is never reached");
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine("Outer catch block handling {0}.", ex.Message);
        }
        finally
        {
            Console.WriteLine("Outer finally block");
        }
    }
}

Lorsque vous exécutez le programme, vous verrez l'ordre exact dans lequel catchet les finallyblocs sont exécutés. Veuillez noter que le code dans le bloc finally après que l'exception est levée ne sera pas exécuté (en fait, dans cet exemple de programme, Visual Studio vous avertira même qu'il a détecté du code inaccessible):

Exception de gestion du bloc catch interne levée par le bloc try.
Inner enfin bloquer
Exception de gestion du bloc catch externe levée du bloc finally.
Outer enfin bloquer

Remarque supplémentaire

Comme l'a souligné Michael Damatov, une exception au trybloc sera "mangée" si vous ne la gérez pas dans un catchbloc (intérieur) . En fait, dans l'exemple ci-dessus, l'exception renvoyée n'apparaît pas dans le bloc de capture externe. Pour rendre cela encore plus clair, regardez l'exemple légèrement modifié suivant:

using System;

class Program
{
    static void Main(string[] args)
    {
        try
        {
            try
            {
                throw new Exception("exception thrown from try block");
            }
            finally
            {
                Console.WriteLine("Inner finally block");
                throw new Exception("exception thrown from finally block");
                Console.WriteLine("This line is never reached");
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine("Outer catch block handling {0}.", ex.Message);
        }
        finally
        {
            Console.WriteLine("Outer finally block");
        }
    }
}

Comme vous pouvez le voir sur la sortie, l'exception interne est "perdue" (c'est-à-dire ignorée):

Inner enfin bloquer
Exception de gestion du bloc catch externe levée du bloc finally.
Outer enfin bloquer
Dirk Vollmar
la source
2
Parce que vous avez levé l'exception dans votre prise intérieure, «Inner finally block» ne sera jamais atteint dans cet exemple
Theofanis Pantelides
4
@Theofanis Pantelides: Non, un finallybloc sera (presque) toujours exécuté, cela vaut également dans ce cas pour le bloc finalement interne (essayez simplement le programme exemple vous-même (un bloc finalement ne sera pas exécuté dans le cas d'un non récupérable) exception, par exemple un EngineExecutionException, mais dans ce cas, votre programme se terminera immédiatement de toute façon.)
Dirk Vollmar
1
Cependant, je ne vois pas quel est le rôle du lancer dans la première capture de votre premier morceau de code. J'ai essayé avec et sans elle avec une application console, aucune différence trouvée.
JohnPan
@johnpan: Le but était de montrer que le bloc finally s'exécute toujours, même si les deux blocs try et catch lèvent une exception. Il n'y a en effet aucune différence dans la sortie console.
Dirk Vollmar
10

S'il y a une exception en attente (lorsque le trybloc a un finallymais pascatch ), la nouvelle exception remplace celle-là.

S'il n'y a aucune exception en attente, cela fonctionne tout comme lancer une exception en dehors du finallybloc.

Guffa
la source
Une exception peut également être en attente s'il est un couplage catchbloc (ré) déclenche une exception.
stakx - ne contribue plus le
4

L'exception se propage.

Darin Dimitrov
la source
2
@bitbonk: de l'intérieur comme d'habitude.
Piskvor a quitté le bâtiment
3

Extrait rapide (et plutôt évident) pour enregistrer "l'exception d'origine" (levée en trybloc) et sacrifier "finalement l'exception" (levée en finallybloc), au cas où l'original est plus important pour vous:

try
{
    throw new Exception("Original Exception");
}
finally
{
    try
    {
        throw new Exception("Finally Exception");
    }
    catch
    { }
}

Lorsque le code ci-dessus est exécuté, "Original Exception" se propage vers le haut de la pile d'appels et "Final Exception" est perdue.

lxa
la source
2

J'ai dû le faire pour intercepter une erreur en essayant de fermer un flux qui n'a jamais été ouvert en raison d'une exception.

errorMessage = string.Empty;

try
{
    byte[] requestBytes = System.Text.Encoding.ASCII.GetBytes(xmlFileContent);

    webRequest = WebRequest.Create(url);
    webRequest.Method = "POST";
    webRequest.ContentType = "text/xml;charset=utf-8";
    webRequest.ContentLength = requestBytes.Length;

    //send the request
    using (var sw = webRequest.GetRequestStream()) 
    {
        sw.Write(requestBytes, 0, requestBytes.Length);
    }

    //get the response
    webResponse = webRequest.GetResponse();
    using (var sr = new StreamReader(webResponse.GetResponseStream()))
    {
        returnVal = sr.ReadToEnd();
        sr.Close();
    }
}
catch (Exception ex)
{
    errorMessage = ex.ToString();
}
finally
{
    try
    {
        if (webRequest.GetRequestStream() != null)
            webRequest.GetRequestStream().Close();
        if (webResponse.GetResponseStream() != null)
            webResponse.GetResponseStream().Close();
    }
    catch (Exception exw)
    {
        errorMessage = exw.ToString();
    }
}

si la requête Web a été créée mais qu'une erreur de connexion s'est produite pendant la

using (var sw = webRequest.GetRequestStream())

puis le finalement attraperait une exception en essayant de fermer les connexions qu'il pensait être ouvertes parce que le webRequest avait été créé.

Si finalement il n'y avait pas de try-catch à l'intérieur, ce code provoquerait une exception non gérée lors du nettoyage de webRequest

if (webRequest.GetRequestStream() != null) 

à partir de là, le code se terminerait sans gérer correctement l'erreur qui s'est produite et donc causer des problèmes pour la méthode d'appel.

J'espère que cela vous servira d'exemple

Emma Grant
la source
1

Le fait de lever une exception alors qu'une autre exception est active entraînera le remplacement de la première exception par la deuxième (plus récente) exception.

Voici un code qui illustre ce qui se passe:

    public static void Main(string[] args)
    {
        try
        {
            try
            {
                throw new Exception("first exception");
            }
            finally
            {
                //try
                {
                    throw new Exception("second exception");
                }
                //catch (Exception)
                {
                    //throw;
                }
            }
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
        }
    }
  • Exécutez le code et vous verrez "deuxième exception"
  • Décommentez les instructions try et catch et vous verrez "première exception"
  • Décommentez également le lancer; et vous verrez à nouveau "deuxième exception".
Doug Coburn
la source
Il convient de noter qu'il est possible pour le nettoyage d'une exception «grave» qui ne serait interceptée qu'en dehors d'un bloc de code particulier de lever une exception qui est interceptée et gérée à l'intérieur. En utilisant des filtres d'exception (disponibles sur vb.net, mais pas C #), il est possible de détecter cette condition. Il n'y a pas grand-chose que le code puisse faire pour le "gérer", bien que si l'on utilise une sorte de framework de journalisation, cela vaut presque certainement la peine d'être journalisé. L'approche C ++ consistant à avoir des exceptions qui se produisent lors du nettoyage déclenchent une fusion du système est moche, mais la disparition des exceptions est à mon humble avis.
supercat
1

Il y a quelques mois, j'ai également fait face à quelque chose comme ça,

    private  void RaiseException(String errorMessage)
    {
        throw new Exception(errorMessage);
    }

    private  void DoTaskForFinally()
    {
        RaiseException("Error for finally");
    }

    private  void DoTaskForCatch()
    {
        RaiseException("Error for catch");
    }

    private  void DoTaskForTry()
    {
        RaiseException("Error for try");
    }


        try
        {
            /*lacks the exception*/
            DoTaskForTry();
        }
        catch (Exception exception)
        {
            /*lacks the exception*/
            DoTaskForCatch();
        }
        finally
        {
            /*the result exception*/
            DoTaskForFinally();
        }

Pour résoudre ce problème, j'ai créé une classe utilitaire comme

class ProcessHandler : Exception
{
    private enum ProcessType
    {
        Try,
        Catch,
        Finally,
    }

    private Boolean _hasException;
    private Boolean _hasTryException;
    private Boolean _hasCatchException;
    private Boolean _hasFinnallyException;

    public Boolean HasException { get { return _hasException; } }
    public Boolean HasTryException { get { return _hasTryException; } }
    public Boolean HasCatchException { get { return _hasCatchException; } }
    public Boolean HasFinnallyException { get { return _hasFinnallyException; } }
    public Dictionary<String, Exception> Exceptions { get; private set; } 

    public readonly Action TryAction;
    public readonly Action CatchAction;
    public readonly Action FinallyAction;

    public ProcessHandler(Action tryAction = null, Action catchAction = null, Action finallyAction = null)
    {

        TryAction = tryAction;
        CatchAction = catchAction;
        FinallyAction = finallyAction;

        _hasException = false;
        _hasTryException = false;
        _hasCatchException = false;
        _hasFinnallyException = false;
        Exceptions = new Dictionary<string, Exception>();
    }


    private void Invoke(Action action, ref Boolean isError, ProcessType processType)
    {
        try
        {
            action.Invoke();
        }
        catch (Exception exception)
        {
            _hasException = true;
            isError = true;
            Exceptions.Add(processType.ToString(), exception);
        }
    }

    private void InvokeTryAction()
    {
        if (TryAction == null)
        {
            return;
        }
        Invoke(TryAction, ref _hasTryException, ProcessType.Try);
    }

    private void InvokeCatchAction()
    {
        if (CatchAction == null)
        {
            return;
        }
        Invoke(TryAction, ref _hasCatchException, ProcessType.Catch);
    }

    private void InvokeFinallyAction()
    {
        if (FinallyAction == null)
        {
            return;
        }
        Invoke(TryAction, ref _hasFinnallyException, ProcessType.Finally);
    }

    public void InvokeActions()
    {
        InvokeTryAction();
        if (HasTryException)
        {
            InvokeCatchAction();
        }
        InvokeFinallyAction();

        if (HasException)
        {
            throw this;
        }
    }
}

Et utilisé comme ça

try
{
    ProcessHandler handler = new ProcessHandler(DoTaskForTry, DoTaskForCatch, DoTaskForFinally);
    handler.InvokeActions();
}
catch (Exception exception)
{
    var processError = exception as ProcessHandler;
    /*this exception contains all exceptions*/
    throw new Exception("Error to Process Actions", exception);
}

mais si vous voulez utiliser des paramètres et renvoyer des types, c'est une autre histoire

Dipon Roy
la source
1
public void MyMethod()
{
   try
   {
   }
   catch{}
   finally
   {
      CodeA
   }
   CodeB
}

La façon dont les exceptions levées par CodeA et CodeB sont traitées est la même.

Une exception levée dans un finallybloc n'a rien de spécial, traitez-la comme la levée d' exception par le code B.

Cheng Chen
la source
Pourriez-vous élaborer? Que voulez-vous dire avec les exceptions sont les mêmes?
Dirk Vollmar
1

L'exception se propage et doit être gérée à un niveau supérieur. Si l'exception n'est pas gérée au niveau supérieur, l'application se bloque. L'exécution du bloc "enfin" s'arrête au point où l'exception est levée.

Qu'il y ait une exception ou non, le bloc "finalement" est garanti pour s'exécuter.

  1. Si le bloc "finalement" est exécuté après qu'une exception s'est produite dans le bloc try,

  2. et si cette exception n'est pas gérée

  3. et si le bloc finalement lève une exception

Ensuite, l'exception d'origine qui s'est produite dans le bloc try est perdue.

public class Exception
{
    public static void Main()
    {
        try
        {
            SomeMethod();
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }

    public static void SomeMethod()
    {
        try
        {
            // This exception will be lost
            throw new Exception("Exception in try block");
        }
        finally
        {
            throw new Exception("Exception in finally block");
        }
    }
} 

Excellent article pour plus de détails

Raj Baral
la source
-1

Il lève une exception;) Vous pouvez intercepter cette exception dans une autre clause catch.

JHollanti
la source