【问题标题】:What happens if a finally block throws an exception?如果 finally 块抛出异常会发生什么?
【发布时间】:2011-02-24 01:40:04
【问题描述】:

如果 finally 块抛出异常,究竟会发生什么?

具体来说,如果在 finally 块中途抛出异常会发生什么。是否会调用此块中的其余语句(之后)?

我知道异常会向上传播。

【问题讨论】:

  • 为什么不试试呢?但是在这种事情上,我最喜欢的是在 finally 之前返回,然后从 finally 块中返回其他东西。 :)
  • finally 块中的所有语句都必须执行。它不能有回报。 msdn.microsoft.com/en-us/library/0hbbzekw(VS.80).aspx

标签: c# exception exception-handling try-catch-finally


【解决方案1】:

如果 finally 块引发异常,究竟会发生什么?

该异常会向外传播,并将(可以)在更高级别进行处理。

您的 finally 块将不会在引发异常的点之后完成。

如果 finally 块在处理较早的异常期间正在执行,则第一个异常将丢失。

C# 4 语言规范第 8.9.5 节:如果 finally 块抛出另一个异常,则终止当前异常的处理。

【讨论】:

  • 除非是ThreadAbortException,否则整个 finally 块将首先完成,因为它是一个临界区。
  • @Shedal - 你是对的,但这仅适用于“某些异步异常”,即 ThreadAbortException。对于普通的 1 线程代码,我的答案是成立的。
  • “第一个异常丢失” - 这实际上非常令人失望,我偶尔会发现 IDisposable 对象在 Dispose() 中抛出异常,这导致异常在“using”子句中丢失。
  • “我发现 IDisposable 对象在 Dispose() 中抛出异常” - 至少可以说很奇怪。阅读 MSDN:AVOID throwing an exception from within Dispose(bool) except under ...
  • @HenkHolterman:磁盘满错误在直接连接的主硬盘上并不常见,但程序有时会将文件写入可移动或网络磁盘;这些问题可能更常见。如果有人在文件完全写入之前拔出 U 盘,最好立即告诉他们,而不是等到他们到达目的地并发现文件已损坏。屈服于较早的错误当有一个错误时可能是明智的行为,但是当没有较早的错误时,报告问题比不报告要好。
【解决方案2】:

对于此类问题,我通常会在 Visual Studio 中打开一个空的控制台应用程序项目并编写一个小示例程序:

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");
        }
    }
}

当您运行程序时,您将看到 catchfinally 块执行的确切顺序。请注意,在抛出异常后的 finally 块中的代码将不会被执行(事实上,在这个示例程序中,Visual Studio 甚至会警告您检测到无法访问的代码):

从 try 块抛出的内部 catch 块处理异常。 内部 finally 块 从 finally 块抛出的外部 catch 块处理异常。 外部 finally 块

补充说明

正如 Michael Damatov 所指出的,如果您不在(内部)catch 块中处理它,try 块中的异常将被“吃掉”。事实上,在上面的示例中,重新抛出的异常并没有出现在外部 catch 块中。为了更清楚地了解以下稍微修改的示例:

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");
        }
    }
}

从输出中可以看出,内部异常是“丢失”(即被忽略):

内部 finally 块 从 finally 块抛出的外部 catch 块处理异常。 外部 finally 块

【讨论】:

  • 因为您在内部捕获中抛出异常,所以在此示例中永远不会到达“内部 finally 块”
  • @Theofanis Pantelides: 不,finally 块将(几乎)总是被执行,这在这种情况下也适用于内部 finally 块(只需自己尝试示例程序(一个 finally 块将在不可恢复的异常情况下不会执行,例如EngineExecutionException,但在这种情况下,您的程序无论如何都会立即终止)。
  • 不过,我看不出在您的第一段代码的第一个捕获中抛出的作用是什么。我用控制台应用程序尝试了使用和不使用它,没有发现差异。
  • @johnpan:重点是表明 finally 块总是执行,即使 try 和 catch 块都抛出异常。控制台输出确实没有区别。
【解决方案3】:

如果有一个异常挂起(当try 块有一个finally 但没有catch),新的异常会替换那个。

如果没有待处理的异常,它就像在finally 块之外抛出异常一样。

【讨论】:

  • 如果存在一个匹配的catch块(重新)抛出异常,则异常也可能处于未决状态。
【解决方案4】:

快速(并且相当明显)sn-p 保存“原始异常”(在try 块中抛出)并牺牲“最终异常”(在finally 块中抛出),以防原始异常对您更重要:

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

执行上述代码时,“原始异常”会向上传播调用堆栈,“最终异常”会丢失。

【讨论】:

    【解决方案5】:

    异常被传播。

    【讨论】:

    • @bitbonk:从内到外,像往常一样。
    【解决方案6】:

    在另一个异常处于活动状态时引发异常将导致第一个异常被第二个(后来的)异常替换。

    这里有一些代码说明会发生什么:

        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);
            }
        }
    
    • 运行代码,你会看到“第二个异常”
    • 取消注释 try 和 catch 语句,您将看到“第一个异常”
    • 还取消注释投掷;声明,您将再次看到“第二个异常”。

    【讨论】:

    • 值得注意的是,清理“严重”异常是可能的,该异常只能在特定代码块之外捕获,以引发在其中捕获和处理的异常。使用异常过滤器(在 vb.net 中可用,但不是 C#)可以检测到这种情况。代码无法“处理”它,但如果使用任何类型的日志记录框架,它几乎肯定值得记录。在清理过程中发生异常触发系统崩溃的 C++ 方法是丑陋的,但是让异常消失是恕我直言的可怕。
    【解决方案7】:

    几个月前我也遇到过这样的事情,

        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();
            }
    

    为了解决这个问题,我做了一个实用类,比如

    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;
            }
        }
    }
    

    这样使用

    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);
    }
    

    但如果您想使用参数和返回类型,那就是另一回事了

    【讨论】:

      【解决方案8】:

      我必须这样做是为了捕捉一个错误,试图关闭一个由于异常而从未打开的流。

      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();
          }
      }
      

      如果 webRequest 已创建,但在此过程中发生连接错误

      using (var sw = webRequest.GetRequestStream())
      

      然后 finally 会捕获一个异常,试图关闭它认为是打开的连接,因为 webRequest 已经创建。

      如果 finally 里面没有 try-catch,那么这段代码在清理 webRequest 时会导致一个未处理的异常

      if (webRequest.GetRequestStream() != null) 
      

      从那里代码将退出而没有正确处理发生的错误,从而导致调用方法出现问题。

      希望这有助于作为一个例子

      【讨论】:

        【解决方案9】:
        public void MyMethod()
        {
           try
           {
           }
           catch{}
           finally
           {
              CodeA
           }
           CodeB
        }
        

        CodeA和CodeB抛出的异常处理方式是一样的。

        finally 块中抛出的异常没有什么特别之处,将其视为代码 B 抛出的异常。

        【讨论】:

        • 您能详细说明一下吗?例外情况相同是什么意思?
        【解决方案10】:

        异常向上传播,应在更高级别进行处理。如果异常未在更高级别处理,则应用程序崩溃。 “finally”块执行在抛出异常的地方停止。

        无论是否有异常,“finally”块都保证执行。

        1. 如果在try块发生异常后正在执行“finally”块,

        2. 如果不处理该异常

        3. 如果finally块抛出异常

        那么在try块中发生的原始异常就丢失了。

        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");
                }
            }
        } 
        

        Great article for Details

        【讨论】:

          【解决方案11】:

          它抛出一个异常;)您可以在其他一些 catch 子句中捕获该异常。

          【讨论】:

            猜你喜欢
            • 2023-04-01
            • 1970-01-01
            • 2012-03-24
            • 2017-02-10
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多