如果finally块引发异常会怎样?


266

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

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

我知道异常会向上传播。


8
为什么不尝试一下呢?但是在这种事情上,我最喜欢的是在finally之前返回,然后从finally块返回其他内容。:)
ANeves 2010年

8
finally块中的所有语句必须执行。它不能有回报。msdn.microsoft.com/zh-CN/library/0hbbzekw(VS.80).aspx
Tim Scarborough,2010年

Answers:


419

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

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

你的finally块不会在引发异常的地方完成。

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

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


9
除非它是一个ThreadAbortException,否则整个finally块将首先完成,因为它是关键部分。
Dmytro Shevchenko 2013年

1
@Shedal-您是对的,但这仅适用于“某些异步异常”,即ThreadAbortException。对于普通的一线程代码,我的答案成立。
Henk Holterman

“丢失了第一个异常”-实际上非常令人失望,偶然地,我发现在Dispose()中抛出异常的IDisposable对象,这导致异常在“ using”子句中丢失。
Alex Burtsev 2014年

“我发现在Dispose()中引发异常的IDisposable对象” –至少可以说很奇怪。在MSDN上阅读:避免从Dispose(bool)内引发异常,除非...
Henk Holterman 2014年

1
@HenkHolterman:磁盘已满错误在直接连接的主硬盘上不是很常见,但是程序有时会将文件写入可移动磁盘或网络磁盘。这些问题可能会更加普遍。如果有人在文件完全写入之前拔出了USB记忆棒,那么最好立即告诉他们,而不是等他们到达要去的地方并发现文件已损坏后再告诉他们。当有一个明智的行为时,会屈服于较早的错误,但是在没有较早的错误的情况下,报告此问题要比不报告该问题更好。
2015年

101

对于此类问题,我通常在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块引发的外部catch块处理异常。
外面终于挡住了

附加说明

正如迈克尔·达马托夫(Michael Damatov)所指出的,try如果不在(内部)catch块中处理该块,则会“吃掉” 该异常。实际上,在上面的示例中,重新抛出的异常没有出现在外部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块引发的外部catch块处理异常。
外面终于挡住了

2
因为您在内部捕获中抛出异常,所以在此示例中永远不会达到“内部最终阻止”
Theofanis Pantelides 2010年

4
@Theofanis Pantelides:不,一个finally块(几乎)将始终执行,在这种情况下,内部的finally块也将保持这种状态(只需自己尝试示例程序(在不可恢复的情况下将不执行finally块)例外,例如EngineExecutionException,但是在这种情况下,您的程序将立即终止)
Dirk Vollmar 2010年

1
不过,我看不出在您的第一段代码的第一个捕获中引发的作用是什么。我尝试将它与控制台应用程序一起使用,也没有,都没有发现差异。
JohnPan

@johnpan:关键是要表明finally块总是执行,即使try和catch块都抛出异常。控制台输出确实没有区别。
Dirk Vollmar '16

10

如果有一个未决异常(当try块中有一个finallyno时catch),则新异常将替换该异常。

如果没有等待处理的异常,它将像在finally块外抛出异常一样工作。


例外,也可能未决的,如果有一匹配catch块(重新)抛出异常。
stakx-不再贡献


3

快速(且相当明显)的代码片段可以保存“原始异常”(以try块形式抛出)并牺牲“最终异常”(以finally块形式抛出),以防原始代码对您更重要:

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

执行上述代码后,“原始异常”将在调用堆栈中传播,并且“最终异常”将丢失。


2

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

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

然后,最终将捕获异常,试图关闭它认为已打开的连接,因为已创建了webRequest。

如果最后在内部没有try-catch,则此代码在清理webRequest时将导致未处理的异常

if (webRequest.GetRequestStream() != null) 

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

希望这可以为例


1

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

以下代码说明了发生的情况:

    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 ++中在清除过程中发生异常会触发系统崩溃的方法很丑陋,但是异常消失却是恕我直言的。
2012年

1

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

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

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


1
public void MyMethod()
{
   try
   {
   }
   catch{}
   finally
   {
      CodeA
   }
   CodeB
}

CodeA和CodeB引发的异常的处理方式相同。

finally块中引发的异常没有什么特别的,将其视为代码B引发的异常。


你能详细说明吗?除了例外,您是什么意思?
德克·沃尔玛

1

异常会传播出去,应在更高级别上进行处理。如果未在更高级别上处理异常,则应用程序将崩溃。“最终”块的执行在引发异常的点停止。

无论是否有异常,“ 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");
        }
    }
} 

伟大的文章详细信息


-1

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.