finally 블록에서 예외가 발생하면 어떻게됩니까?


266

finally 블록에서 예외가 발생하면 정확히 어떻게됩니까?

특히, 예외가 finally 블록의 중간에 발생하면 어떻게됩니까? 이 블록의 나머지 부분 (이후)이 호출됩니까?

예외가 위쪽으로 전파된다는 것을 알고 있습니다.


8
그냥 시도해 보지 않겠습니까? 그러나 이런 종류의 것들에서, 내가 가장 좋아하는 것은 finally 전에 반환 된 다음 finally 블록에서 다른 것을 반환합니다. :)
ANeves

8
finally 블록의 모든 문을 실행해야합니다. 반품 할 수 없습니다. msdn.microsoft.com/ko-kr/library/0hbbzekw(VS.80).aspx
Tim Scarborough

답변:


419

finally 블록에서 예외 가 발생 하면 정확히 어떻게됩니까?

그 예외는 계속해서 전파되고 더 높은 수준에서 처리 될 수 있습니다.

최종 차단은 예외가 발생한 지점을 넘어 완료 되지 않습니다 .

이전 예외를 처리하는 동안 finally 블록이 실행 된 경우 첫 번째 예외가 손실됩니다.

C # 4 언어 사양 § 8.9.5 : finally 블록에서 다른 예외가 발생하면 현재 예외 처리가 종료됩니다.


9
그것이 아닌 한 ThreadAbortException, finally 섹션 전체가 중요한 섹션이므로 먼저 완료됩니다.
Dmytro Shevchenko

1
@Shedal-맞습니다. 그러나 "확실한 비동기 예외", 즉 ThreadAbortException에만 적용됩니다. 일반적인 1 스레드 코드의 경우 내 대답이 유지됩니다.
Henk Holterman

"첫 번째 예외가 유실되었습니다"-실제로 매우 실망스러운 경우가 있습니다. 때로는 Dispose ()에서 예외를 발생시키는 IDisposable 객체가 발견되어 "using"절에서 예외가 손실됩니다.
Alex Burtsev

"Dispose ()에서 예외를 발생시키는 IDisposable 객체를 찾았습니다." -가장 이상하게 말하면 이상합니다. MSDN에 읽기 : 한주의 사항을 제외하고 폐기 (부울) 내에서 예외를 던지는 ...
헹크 Holterman에게

1
@HenkHolterman : 디스크 전체 오류는 직접 연결된 기본 하드 디스크에서는 흔하지 않지만 프로그램은 때때로 이동식 또는 네트워크 디스크에 파일을 씁니다. 그 문제는 훨씬 더 일반적 일 수 있습니다. 파일이 완전히 기록되기 전에 누군가 USB 스틱을 잡아 당기면, 그들이 어디로 가고 파일이 손상 될 때까지 기다리는 것보다 즉시 알려주는 것이 좋습니다. 이전 오류에 항복하는 것은 이 한 때 현명한 행동 일 수 있지만 이전 오류가 없을 때 그것을보고되지 않은 휴가보다 문제를보고하는 것이 좋습니다 것입니다.
supercat

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블록 에서 처리하지 않으면 블록 의 예외 가 "먹게"됩니다 . 실제로 위의 예에서 재발 사 예외는 외부 캐치 블록에 나타나지 않습니다. 다음과 같이 약간 수정 된 샘플을보다 명확하게 확인하십시오.

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
당신은 당신의 내면의 캐치에 예외를 발생하기 때문에, '내부 finally 블록'이 예에서 도달하지 않을 것이다
테오 Pantelides

4
@ Theofanis Pantelides : 아니오, finally블록은 (거의) 항상 실행됩니다.이 경우에도 내부 finally 블록에 대해서도 유지됩니다 (샘플 프로그램을 직접 시도하십시오 (복구 할 수없는 경우에는 마지막으로 블록이 실행되지 않습니다) 예외, 예를 들어 EngineExecutionException, 그러나이 경우 프로그램은 즉시 종료됩니다)
Dirk Vollmar

1
그러나 첫 번째 코드 조각의 첫 번째 캐치에서 던지기의 역할이 무엇인지 알 수 없습니다. 콘솔 응용 프로그램을 사용하거나 사용하지 않고 시도했지만 차이점이 없습니다.
JohnPan

@ johnpan : try와 catch 블록이 모두 예외를 throw하더라도 finally 블록이 항상 실행된다는 것을 보여주었습니다. 실제로 콘솔 출력에는 차이가 없습니다.
Dirk Vollmar

10

보류중인 예외가있는 경우 ( try블록이 finally있지만 아니오 인 경우 catch) 새 예외가 해당 예외를 대체합니다.

보류중인 예외가 없으면 finally블록 외부에서 예외를 throw하는 것처럼 작동합니다 .


예외를 다시 발생 시키는 일치하는 블록 있는 경우 예외가 보류 중일 수도 있습니다 catch.
stakx-더 이상


3

원래 예외 가 더 중요한 경우 "원래 예외"( try블록 에서 발생)를 저장 하고 "최종 예외"(블록 에서 발생)를 저장하는 빠른 (그리고 명백한) 스 니펫 finally:

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

위의 코드가 실행되면 "Original Exception"이 호출 스택을 전파하고 "Finally Exception"이 손실됩니다.


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 문을 주석 해제하면 "첫 번째 예외"가 표시됩니다
  • 또한 던지기의 주석 처리를 제거하십시오. "두 번째 예외"가 다시 나타납니다.

특정 코드 블록 외부에서만 발견되는 "심각한"예외를 정리하여 예외를 포착하고 처리 할 수 ​​있다는 점은 주목할 가치가 있습니다. 예외 필터 (C #은 아니지만 vb.net에서 사용 가능)를 사용하면이 조건을 감지 할 수 있습니다. 어떤 종류의 로깅 프레임 워크를 사용하는 경우 거의 확실하게 로깅 할 가치가 있지만 코드가 "처리"하기 위해 수행 할 수있는 작업은 많지 않습니다. 정리 트리거 내에서 예외가 발생하여 시스템 붕괴가 발생하는 C ++ 접근 방식은 추악하지만 예외가 사라지는 것은 IMHO 끔찍합니다.
supercat

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에 의해 예외로 처리됩니다.


좀 더 자세히 설명해 주시겠습니까? 예외가 동일하다는 것은 무엇을 의미합니까?
Dirk Vollmar

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 절에서 해당 예외를 잡을 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.