Le code dans une instruction Final se déclenchera-t-il si je renvoie une valeur dans un bloc Try?

237

J'examine un code pour un ami et dis qu'il utilisait une déclaration de retour à l'intérieur d'un bloc try-finally. Le code de la section Enfin se déclenche-t-il même si le reste du bloc try ne fonctionne pas?

Exemple:

public bool someMethod()
{
  try
  {
    return true;
    throw new Exception("test"); // doesn't seem to get executed
  }
  finally
  {
    //code in question
  }
}
JamesEggers
la source
Être manipulé ici signifie: attrapé. Même un bloc catch vide dans votre gestionnaire global suffit. De plus, il existe des exceptions qui ne peuvent pas être gérées: StackOverflowExceptionen ExecutionEngineExceptionsont certaines. Et comme ils ne peuvent pas être traités, finallyils ne fonctionneront pas.
Abel
8
@Abel: Vous semblez parler d'une situation différente. Cette question concerne le retour dans un trybloc. Rien sur un programme brutal n'est abandonné.
Jon Skeet
6
@Abel: Je ne sais pas trop ce que vous entendez par "revenir après une exception", mais cela ne semble pas être ce qui est demandé ici. Regardez le code - la première instruction du trybloc est une returninstruction. (La deuxième déclaration de ce bloc est inaccessible et générera un avertissement.)
Jon Skeet
4
@Abel: En effet, et si la question avait été "Will codera-t-il dans une instruction finally toujours exécutable dans chaque situation", cela aurait été pertinent. Mais ce n'est pas ce qui était demandé.
Jon Skeet

Réponses:

265

Réponse simple: oui.

Andrew Rollings
la source
9
@Edi: Um, je ne vois pas ce que les fils d'arrière-plan ont à voir avec ça. Fondamentalement, à moins que l'ensemble du processus n'abandonne, le finallybloc s'exécute.
Jon Skeet
3
La réponse longue est que vous n'obtiendrez pas nécessairement un bloc enfin à exécuter si quelque chose de catastrophique s'est produit, comme un débordement de pile, une exception de mémoire insuffisante, un crash dur d'une sorte ou si quelqu'un débranche votre machine au bon moment . Mais à toutes fins utiles, à moins que vous ne fassiez quelque chose de très très mal, le bloc finalement se déclenchera toujours.
Andrew Rollings du
Si le code avant l'essai échoue pour une raison quelconque, j'ai remarqué que finalement il est toujours exécuté. Dans ce cas, vous pouvez ajouter une condition pour in finally qui remplit les critères pour s'exécuter finalement uniquement lorsque le code sous s'exécute avec succès
kjosh
200

Normalement, oui. La section finally est garantie d'exécuter tout ce qui se passe, y compris les exceptions ou l'instruction return. Une exception à cette règle est une exception asynchrone se produisant sur le thread ( OutOfMemoryException, StackOverflowException).

Pour en savoir plus sur les exceptions asynchrones et le code fiable dans ces situations, consultez les régions d'exécution contraintes .

Mehrdad Afshari
la source
154

Voici un petit test:

class Class1
{
    [STAThread]
    static void Main(string[] args)
    {
        Console.WriteLine("before");
        Console.WriteLine(test());
        Console.WriteLine("after");
    }

    static string test()
    {
        try
        {
            return "return";
        }
        finally
        {
            Console.WriteLine("finally");
        }
    }
}

Le résultat est:

before
finally
return
after
Jon B
la source
10
@CodeBlend: Cela a à voir avec le moment où WriteLinele résultat de l'appel de méthode est réellement exécuté. Dans ce cas, l' returnappel définit le résultat des méthodes, écrit finalement sur la console - avant la fin de la méthode. Ensuite, WriteLinedans la méthode Main crache le texte de l'appel de retour.
NotMe
38

Citation de MSDN

enfin est utilisé pour garantir l'exécution d'un bloc d'instructions de code, quelle que soit la manière dont le bloc d' essai précédent est quitté .

Perpetualcoder
la source
3
Eh bien, en plus des cas exceptionnels de Mehrdad, enfin ne sera pas appelé si vous déboguez dans un bloc d'essai, puis arrêtez le débogage :). Rien dans la vie n'est garanti, semble-t-il.
StuartLC
1
Pas tout à fait, citant MSDN : Cependant, si l'exception n'est pas gérée, l'exécution du bloc finally dépend de la façon dont l'opération de déroulement de l'exception est déclenchée. Cela dépend à son tour de la configuration de votre ordinateur. .
Abel
1
@Abel fait un point crucial ici. Tout le monde peut voir comment un bloc finally n'est pas exécuté si, par exemple, le programme est abandonné via le gestionnaire de tâches. Mais cette réponse est, en l'état, tout finallyà fait fausse: en fait, elle ne garantit rien, même pour des programmes parfaitement ordinaires (ce qui est arrivé à jeter cette exception).
Peter - Rétablir Monica le
19

En règle générale, oui, le enfin s'exécute.

Pour les trois scénarios suivants, le enfin fonctionnera TOUJOURS :

  1. Aucune exception ne se produit
  2. Exceptions synchrones (exceptions qui se produisent dans le flux de programme normal).
    Cela inclut les exceptions conformes CLS qui dérivent de System.Exception et les exceptions non conformes CLS, qui ne dérivent pas de System.Exception. Les exceptions non conformes à CLS sont automatiquement encapsulées par RuntimeWrappedException. C # ne peut pas lever d'exceptions de réclamation non CLS, mais les langages tels que C ++ le peuvent. C # pourrait appeler du code écrit dans un langage qui peut lever des exceptions non conformes CLS.
  3. ThreadAbortException asynchrone À
    partir de .NET 2.0, une ThreadAbortException n'empêchera plus finalement un de s'exécuter. ThreadAbortException est maintenant hissé avant ou après le finalement. Le finalement s'exécutera toujours et ne sera pas interrompu par un abandon de thread, tant que l'essai a été réellement entré avant que l'abandon du thread ne se produise.

Le scénario suivant, enfin, ne s'exécutera pas:

StackOverflowException asynchrone.
À partir de .NET 2.0, un débordement de pile entraînera la fin du processus. Le finally ne sera pas exécuté, à moins qu'une autre contrainte ne soit appliquée pour faire enfin du CER une région d'exécution contrainte. Les CER ne doivent pas être utilisés dans le code utilisateur général. Ils ne doivent être utilisés que s'il est essentiel que le code de nettoyage s'exécute toujours - une fois que tout le processus s'est arrêté en cas de débordement de pile et que tous les objets gérés seront donc nettoyés par défaut. Ainsi, le seul endroit où une CER devrait être pertinente est pour les ressources qui sont allouées en dehors du processus, par exemple, les descripteurs non gérés.

En règle générale, le code non managé est encapsulé par une classe gérée avant d'être consommé par le code utilisateur. La classe wrapper managé utilise généralement un SafeHandle pour encapsuler le handle non managé. SafeHandle implémente un finaliseur critique et une méthode Release exécutée dans une CER pour garantir l'exécution du code de nettoyage. Pour cette raison, vous ne devriez pas voir les URCE jonchées de code utilisateur complet.

Ainsi, le fait que finalement ne s'exécute pas sur StackOverflowException ne devrait pas avoir d'effet sur le code utilisateur, car le processus se terminera de toute façon. Si vous avez un cas limite où vous devez nettoyer une ressource non managée, en dehors d'un SafeHandle ou CriticalFinalizerObject, utilisez un CER comme suit; mais veuillez noter qu'il s'agit d'une mauvaise pratique - le concept non managé doit être abstrait pour une ou plusieurs classes gérées et des SafeHandle appropriés par conception.

par exemple,

// No code can appear after this line, before the try
RuntimeHelpers.PrepareConstrainedRegions();
try
{ 
    // This is *NOT* a CER
}
finally
{
    // This is a CER; guaranteed to run, if the try was entered, 
    // even if a StackOverflowException occurs.
}
markn
la source
Veuillez noter que même un CER ne fonctionnera pas dans le cas d'un SOE. Au moment où vous avez rédigé cela, la documentation MSDN sur CER était incorrecte / incomplète. Un SOE déclenchera un en FailFastinterne. La seule façon dont j'ai réussi à les attraper est de personnaliser l'hébergement d'exécution CLR . Notez que votre point est toujours valide pour certaines autres exceptions asynchrones.
Abel
10

Il y a une exception très importante à cela que je n'ai pas vue mentionnée dans d'autres réponses, et que (après avoir programmé en C # pendant 18 ans) je ne peux pas croire que je ne savais pas.

Si vous lancez ou déclenchez une exception de quelque sorte à l'intérieur de votre catchbloc (pas seulement bizarre StackOverflowExceptionset des choses de ce genre), et que vous n'avez pas le try/catch/finallybloc entier dans un autre try/catchbloc, votre finallybloc ne s'exécutera pas. Cela est facilement démontré - et si je ne l'avais pas vu moi-même, étant donné la fréquence à laquelle j'ai lu que ce ne sont que des cas d'angle vraiment étranges et minuscules qui peuvent empêcher un finallybloc de s'exécuter, je ne l'aurais pas cru.

static void Main(string[] args)
{
    Console.WriteLine("Beginning demo of how finally clause doesn't get executed");
    try
    {
        Console.WriteLine("Inside try but before exception.");
        throw new Exception("Exception #1");
    }
    catch (Exception ex)
    {
        Console.WriteLine($"Inside catch for the exception '{ex.Message}' (before throwing another exception).");
        throw;
    }
    finally
    {
        Console.WriteLine("This never gets executed, and that seems very, very wrong.");
    }

    Console.WriteLine("This never gets executed, but I wasn't expecting it to."); 
    Console.ReadLine();
}

Je suis sûr qu'il y a une raison à cela, mais c'est bizarre que ce ne soit pas plus largement connu. (C'est noté ici par exemple, mais pas n'importe où dans cette question particulière.)

Ken Smith
la source
Eh bien, le finallybloc n'est tout simplement pas un piège pour le catchbloc.
Peter - Rétablir Monica
7

Je me rends compte que je suis en retard à la fête mais dans le scénario (différent de l'exemple de l'OP) où en effet une exception est levée États MSDN ( https://msdn.microsoft.com/en-us/library/zwc8s4fz.aspx ): "Si l'exception n'est pas interceptée, l'exécution du bloc finally dépend de la décision du système d'exploitation de déclencher une opération de déroulement d' exception."

L' exécution du bloc finally n'est garantie que si une autre fonction (telle que Main) plus haut dans la pile des appels intercepte l'exception. Ce détail n'est généralement pas un problème car tous les environnements d'exécution (CLR et OS) C # s'exécutent sur la plupart des ressources libres qu'un processus possède à sa fermeture (descripteurs de fichiers, etc.). Dans certains cas, cela peut être crucial cependant: Une opération de base de données à moitié en cours que vous souhaitez valider resp. se détendre; ou une connexion à distance qui peut ne pas être fermée automatiquement par le système d'exploitation, puis bloque un serveur.

Peter - Rétablir Monica
la source
3

Oui. C'est en fait ce point principal d'une déclaration finale. À moins que quelque chose ne se produise (par manque de mémoire, ordinateur débranché, etc.), l'instruction finally doit toujours être exécutée.

Jeffrey L Whitledge
la source
1
Je vous prie de ne pas être d'accord. Cf. ma réponse. Une exception parfaitement normale dans le bloc try est suffisante pour contourner le finallybloc si cette exception n'est jamais interceptée. Cela m'a pris par surprise ;-).
Peter - Rétablir Monica
@ PeterA.Schneider - C'est intéressant. Bien sûr, les implications de cela ne changent pas vraiment beaucoup. Si rien dans la pile d'appels ne va intercepter l'exception, cela devrait signifier (si je comprends bien) que le processus est sur le point de se terminer. C'est donc similaire à la situation de tirer la fiche. Je suppose que la conclusion à tirer de cela est que vous devriez toujours avoir une capture d'exception de niveau supérieur ou non gérée.
Jeffrey L Whitledge
C'est exactement ce que je comprends également. J'ai aussi trouvé cela surprenant. Vrai: Les ressources les plus courantes seront libérées automatiquement, en particulier la mémoire et les fichiers ouverts. Mais les ressources que le système d'exploitation ne connaît pas - les connexions ouvertes au serveur ou à la base de données par exemple - peuvent rester ouvertes du côté distant car elles n'ont jamais été correctement fermées; les sockets persistent, etc. Je suppose qu'il est prudent d'avoir un gestionnaire d'exceptions de niveau supérieur, oui.
Peter - Rétablir Monica
2

ne fonctionnera finalement pas si vous quittez l'application en utilisant System.exit (0); un péché

try
{
    System.out.println("try");
    System.exit(0);
}
finally
{
   System.out.println("finally");
}

le résultat serait juste: essayez

Hakuna Matata
la source
4
Vous répondez dans la mauvaise langue, je suppose, la question portait sur c#, mais cela semble être le cas Java. Et en plus de cela, dans la plupart des cas, System.exit()c'est un soupçon de mauvaise conception :-)
z00l
0

Dans 99% des scénarios, il sera garanti que le code à l'intérieur du finallybloc s'exécutera, cependant, pensez à ce scénario: vous avez un thread qui a un try-> finallybloc (non catch) et vous obtenez une exception non gérée dans ce thread. Dans ce cas, le thread se fermera et son finallybloc ne sera pas exécuté (l'application peut continuer à s'exécuter dans ce cas)

Ce scénario est assez rare, mais c'est seulement pour montrer que la réponse n'est pas TOUJOURS "Oui", c'est la plupart du temps "Oui" et parfois, dans des conditions rares, "Non".

Jonathan Perry
la source
0

Le but principal de finalement block est d'exécuter tout ce qui y est écrit. Cela ne devrait pas dépendre de ce qui se passe dans try ou catch.Cependant, avec System.Environment.Exit (1), l'application se fermera sans passer à la ligne de code suivante.

Ashish Sahu
la source