성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] VT sequences to "CONOUT$" vs. STD_O...
[정성태] NetCoreDbg is a managed code debugg...
[정성태] Evaluating tail call elimination in...
[정성태] What’s new in System.Text.Json in ....
[정성태] What's new in .NET 9: Cryptography ...
[정성태] 아... 제시해 주신 "https://akrzemi1.wordp...
[정성태] 다시 질문을 정리할 필요가 있을 것 같습니다. 제가 본문에...
[이승준] 완전히 잘못 짚었습니다. 댓글 지우고 싶네요. 검색을 해보...
[정성태] 우선 답글 감사합니다. ^^ 그런데, 사실 저 예제는 (g...
[이승준] 수정이 안되어서... byteArray는 BYTE* 타입입니다...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<br /> <pre class="code"> "처리되지 않은 모든 예외는 AppDomain.UnhandledException 이벤트를 통해 잡아낼 수 있다." </pre> <br /> 기본 전제는 위와 같습니다. 위의 사항에서부터 하나씩 정리해 보도록 하겠습니다.<br /> <br /> 우선. Console Application 예를 들어볼까요?<br /> 사실 Console Application이야말로 가장 군더더기 없는 순수한 프로젝트 유형이라고 할 수 있지요. ^^<br /> <br /> 다음과 같은 코드는 Console 유형의 C# 응용 프로그램 코드입니다.<br /> <br /> <pre class="code"> namespace ConsoleException { class ExceptionClass { <b>~ExceptionClass() { throw new ApplicationException("Exception at Finalizer"); } </b> } class Program { static void Main(string[] args) { <b>AppDomain.CurrentDomain.UnhandledException += delegate(object sender, UnhandledExceptionEventArgs e) { Console.WriteLine("AppDomain.CurrentDomain.UnhandledException"); };</b> { new ExceptionClass(); } <b>throw new ApplicationException("Exception at Main");</b> // 강제로 GC를 구동. 따라서 ExceptionClass의 Finalizer가 호출됨. GC.Collect(); <b>System.Threading.ThreadPool.QueueUserWorkItem(Program.ThreadCallback);</b> System.Console.Read(); } static public void ThreadCallback(object state) { <b>throw new ApplicationException("Exception at ThreadCallback");</b> } } } </pre> <br /> 위의 코드를 보면, 예외가 발생하는 곳이 총 3군데 있습니다. (예외 발생 순서마다 하나씩 주석 처리하시면 모든 예외를 테스트하실 수 있습니다.)<br /> <br /> 1. Finalizer에서 예외 발생<br /> 2. Main Thread에서 예외 발생<br /> 3. Thread Pool Thread에서 예외 발생<br /> <br /> 모든 예외가 동일하게 AppDomain.CurrentDomain.UnhandledException에서 걸리는 것을 확인할 수 있습니다. 일단, 콘솔 응용 프로그램만큼은 "처리되지 않은 모든 예외는 AppDomain.UnhandledException 이벤트를 통해 잡아낼 수 있다."에서 벗어나지 않는군요. ^^<br /> <br /> <hr /> <a name='thread_exception'></a> <br /> 그런데, 왜 WinForm과 ASP.NET은 AppDomain.UnhandledException에서 예외가 처리되지 않는 걸까요?<br /> 이 대답은, 별도의 try / catch가 처리를 하기 때문에 AppDomain.UnhandledException까지 넘어오지 않기 때문입니다. 윈폼이라고 뭐 특별한 것이 있겠습니까? ^^<br /> <br /> 그런 이유로 윈폼의 규칙을 정리해 보면, <br /> secondary thread에서 발생하는 예외는 정상적으로 AppDomain.UnhandledException으로 이벤트가 발생되어 넘어오지만,<br /> primary thread에서 발생하는 예외는 Application 클래스 자체에서 걸어둔 예외 핸들러로 인해 AppDomain.UnhandledException까지 넘어가지 않습니다. 대신에 자체적으로 예외가 발생했음을 알리는 Application.ThreadException 이벤트를 가지고 있습니다.<br /> <br /> 아시는 것처럼, WinForm은 직접적으로 사용자 코드를 실행시키지 않고 다음과 같이 Application에 Form 클래스를 넘겨주는 것으로 시작하는 것을 볼 수 있습니다.<br /> <br /> <pre class="code"> [STAThread] static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(<b>new Form1()</b>); } </pre> <br /> 즉, 여러분 역시 이러한 기법으로 Console Application을 만든다면, 여러분만의 프레임워크 진입 클래스를 만들어서 그 자체에서 예외를 처리할 수 있도록 만들 수 있다는 것입니다. 바로 이러한 이유로 다른 곳도 아닌, "Application" 클래스에서 "ThreadException" 이벤트를 제공해 주고 있는 것입니다.<br /> 원래 Application 클래스만 없었다면 Form1을 처리하는 primary thread에서 발생하는 예외 역시 AppDomain.UnhandledException에서 처리되었을 수 있었다는 얘기지요.<br /> <br /> 그럼,,, 좀 명확해 지셨나요? <br /> <br /> 여기서, 잠시 .NET 1.1과 .NET 2.0의 예외 처리 방식을 비교해 볼 필요가 있겠습니다.<br /> <br /> .NET 1.1의 WinForm App에서는 secondary thread에서 예외가 발생하는 경우 AppDomain.UnhandledException에서는 try / catch를 통해서 에외를 먹어버리고 그냥 넘어가 버렸습니다. <br /> 사실, 어떻게 생각해 보면 그것이 옳았을 수도 있습니다. 이미 WinForm App는 콘솔 응용 프로그램과는 달리 상당히 복잡해진 구조를 띄고 있으며, 그렇게 덩치 큰 응용 프로그램이 2차 스레드에서 발생하는 조그만 예외 하나 처리 못했다고 죽어 버린다는 것은 용납될 수 없었기 때문일 것입니다.<br /> <br /> 하지만, .NET 2.0에서는, 명확하게 그 부분을 rethrow하고 있습니다. 즉, 2차 스레드에서의 예외 역시 응용 프로그램을 종료해버리게 만드는 구조로 바뀐 것입니다. (일례로 throw [exobject];만 한 줄 더 써주었을 것입니다.)<br /> .NET 1.1에서 다소 느슨하게 예외처리가 되었던 WinForm Application들은 이제 .NET 2.0에 와서는 그러한 취약 부분의 코드로 인해 응용 프로그램이 종료할 수 있는 상황이 발생해 버렸습니다.<br /> <br /> 그런 경우, 우선 취할 수 있는 방법이 .NET 1.1처럼 예외를 무시하도록 만들어서 프로그램을 당장 돌아갈 수 있는 상태로 만드는 것입니다. 이를 위해 .NET 2.0에서는 다음과 같은 Config 설정으로 가능하게 하고 있습니다.<br /> <br /> <pre class="code"> <configuration> <runtime> <b><legacyUnhandledExceptionPolicy enabled="1" /> </b> </runtime> <configuration> </pre> <br /> 일단, 이렇게 만들고 난 후 정확히 어느 부분에서 예외가 발생하는지를 점검해야 할 텐데요. 간단합니다. 모든 예외는 AppDomain.UnhandledException에서 처리된다고 이미 말씀드린 것을 기억하시겠지요.<br /> 기존 코드에서 다음과 같이 UnhandledException을 처리하는 예외 핸들러를 넣고 그 안에 로그를 남기도록 구현해 줍니다.<br /> <br /> <pre class="code"> [STAThread] static void Main() { <b>AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);</b> Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(<b>new Form1()</b>); } </pre> <br /> 그렇게 하나씩, secondary thread에서 예외가 발생하는 것을 없애고, 나중에는 legacyUnhandledExceptionPolicy[@enabled] 값을 다시 0으로 주거나, 아예 삭제해 버리면 됩니다.<br /> 물론, 정책에 따라서는 그냥 남겨두어도 무방하겠지요. 만약 저더러 권해 달라고 한다면, 아마 다음과 같이 말하고 싶을 것 같습니다.<br /> <br /> <pre class="code"> "Application 개발을 릴리스해서 유지 보수를 활발히 진행할 수 있는 동안에는 legacyUnhandledExceptionPolicy[@enabled] 값을 0으로 설정해 주십시오. 그러다, 유지 보수 계약까지 완료한 이후에는 legacyUnhandledExceptionPolicy[@enabled] 값을 1로 바꿉니다. 물론 발생하는 예외에 대해서는 반드시 로그에 남겨줍니다." </pre> <br /> 위의 의견이 마음에 드세요? ^^<br /> <br /> .NET 2.0에서 바뀐 것은 이뿐만이 아닙니다. 기존의 Application 클래스가 primary thread에서 처리하던 예외를 다시 rethrow하는 기능을 옵션으로 제공해 주도록 바뀐 것입니다. 거기서 rethrow하면 어떻게 될까요? 당연히 ^^ 그 이후에는 AppDomain.UnhandledException에서 처리를 하게 되겠지요.<br /> 일관성 있는 예외 처리라는 측면에서도 이는 바람직한 코드가 될 것 같습니다. 방식은 다음과 같습니다. (반드시 Run 메서드 호출 이전에 처리해 주어야 한다는군요.)<br /> <br /> <pre class="code"> [STAThread] static void Main() { <b>Application.SetUnhandledExceptionMode(UnhandledExceptionMode.ThrowException);</b> Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new Form1()); } </pre> <br /> SetUnhandledExceptionMode의 인자 유형에 대해서는 나중에 기회되면 좀 더 다루도록 하겠습니다.<br /> <br /> <hr /> <br /> 자신만의 예외 처리기를 구현한 것은 WinForm만이 아닙니다. ASP.NET 역시 Request 처리에 대해 try / catch를 걸어두고 HttpApplication 클래스에서 나름대로 처리를 하고 있습니다.<br /> 따라서, 기본 골격은 WinForm App와 비슷한 구조를 유지하고 있습니다. <br /> <br /> Request를 처리하는 스레드에서 발생한 예외 : HttpApplication.Error <br /> 그 이외의 스레드에서 발생한 예외 : AppDomain.UnhandledException<br /> <br /> 그 이외의 구현도 ASP.NET은 WinForm의 상황과 다소 비슷합니다. .NET 1.1에서는 AppDomain.UnhandledException에서 예외를 먹어 버렸지만, .NET 2.0에서는 응용 프로그램을 종료시켜 버립니다. 이로 인해, w3wp.exe는 종료되고 IIS AppPool 관리자에 의해서 새로운 w3wp.exe가 생성되어 서비스를 시작합니다.<br /> 그것을 막기 위해 web.config에 legacyUnhandledExceptionPolicy[@enabled] 설정값을 "1"로 주는 것도 동일합니다.<br /> <br /> 다른 것이 있다면 2가지 정도가 떠오르는데요.<br /> 첫 번째, WinForm은 Main 함수에 개발자가 임의대로 코드를 주어 AppDomain.CurrentDomain.UnhandledException을 줄 수 있었지만, ASP.NET의 경우에는 그것이 불가능하다는 것입니다. 대신에 Microsoft에서는 HttpModule에서 이를 처리하도록 권고하고 있는데요. 다음과 같은 토픽에서 이에 대해 자세히 설명해 주고 있습니다.<br /> <br /> Unhandled exceptions cause ASP.NET-based applications to unexpectedly quit in the .NET Framework 2.0<br /> ; <a target='_blank' href='https://learn.microsoft.com/en-us/troubleshoot/aspnet/exceptions-cause-apps-quit'>https://learn.microsoft.com/en-us/troubleshoot/aspnet/exceptions-cause-apps-quit</a><br /> <br /> 두 번째는, ASP.NET에서는 WinForm의 Application.SetUnhandledExceptionMode와 같은 기능은 지원하지 않는다는 것입니다. 따라서 WinForm처럼, HttpApplication에서의 예외 처리를 AppDomain.UnhandledException에 넘겨서 예외를 일괄 처리할 수 있는 방법은 없습니다. <br /> ASP.NET 2.0에서 반가운 소식이 한 가지 있다면, 이제 Request 처리 시에 발생하는 예외에 대해서는 Windows Event Log에 상세하게 로그 기록이 남겨진다는 것입니다. 반면에 아쉬운 소식이 있다면, ^^ 그 이외의 thread에서 발생하는 예외의 경우에는 역시 이벤트 로그에 남겨주기는 하지만 그다지 상세한 로그를 남겨주지는 않는다는 점입니다. 아래는 실제로 별도의 스레드에서 예외가 발생했을 때 남겨진 이벤트 로그 내용입니다.<br /> <br /> <pre class="code"> Event Type: Error Event Source: .NET Runtime 2.0 Error Reporting Event Category: None Event ID: 5000 Date: 8/9/2006 Time: 11:13:49 AM User: N/A Computer: SEDONA Description: EventType clr20r3, P1 w3wp.exe, P2 6.0.3790.1830, P3 42435be1, P4 app_web_0mz2jpmu, P5 0.0.0.0, P6 44d9448b, P7 4, P8 b, P9 system.applicationexception, P10 NIL. For more information, see Help and Support Center at http://go.microsoft.com/fwlink/events.asp. Data: 0000: 63 00 6c 00 72 00 32 00 c.l.r.2. 0008: 30 00 72 00 33 00 2c 00 0.r.3.,. ; [중간 생략] ; 00e0: 6f 00 6e 00 20 00 4e 00 o.n. .N. 00e8: 49 00 4c 00 0d 00 0a 00 I.L..... </pre> <br /> 이보다 더 상세한 로그를 원하신다면, 위의 첫 번째 방법에서 소개했던 "<a target='_blank' href='https://learn.microsoft.com/en-us/troubleshoot/aspnet/exceptions-cause-apps-quit'>https://learn.microsoft.com/en-us/troubleshoot/aspnet/exceptions-cause-apps-quit</a>"에서 제시하는 방법을 사용하면 가능합니다.<br /> <br /> <hr /> <br /> [내용추가]<br /> 2006.09.07:<br /> 위의 내용 중에서 제가 실수를 한 것이 있습니다. legacyUnhandledExceptionPolicy 요소에 대한 설정을 하는 부분에서 그것을 web.config에 해야 한다고 써놓았는데, web.config이 아닌 "%WINDIR%\Microsoft.NET\Framework\v2.0.50727\Aspnet.config" 파일에 넣어두어야 합니다.<br /> <br /> <hr /> 2006.09.20 : [내용 추가] ASP.NET 관련해서 예외 처리 정리를 다음의 토픽에 실었으니 참고하십시오.<br /> CLR & Debug Features : 3.14.1 ASP.NET 디버깅 환경 구성<br /> ; <a target="_blank" href="/2/0/342">http://www.sysnet.pe.kr/2/0/342 </a> <br /> <br /><br /><hr /><span style='color: Maroon'>[이 토픽에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</a>
첨부파일
스팸 방지용 인증 번호
2099
(왼쪽의 숫자를 입력해야 합니다.)