Microsoft MVP성태의 닷넷 이야기
.NET Framework: 538. Thread.Abort로 인해 프로세스가 종료되는 현상 [링크 복사], [링크+제목 복사],
조회: 27280
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
 

(시리즈 글이 5개 있습니다.)
.NET Framework: 284. Thread 개체의 Interrupt와 Abort의 차이점
; https://www.sysnet.pe.kr/2/0/1205

.NET Framework: 418. Thread.Abort 호출의 hang 현상
; https://www.sysnet.pe.kr/2/0/1617

.NET Framework: 536. Thread.Abort의 스레드 종료 지연
; https://www.sysnet.pe.kr/2/0/10865

.NET Framework: 538. Thread.Abort로 인해 프로세스가 종료되는 현상
; https://www.sysnet.pe.kr/2/0/10867

디버깅 기술: 109. windbg - (x64에서의 인자 값 추적을 이용한) Thread.Abort 시 대상이 되는 스레드를 식별하는 방법
; https://www.sysnet.pe.kr/2/0/11380




Thread.Abort로 인해 프로세스가 종료되는 경우

"제프리 리처의 CLR via C#" 책을 보면,

제프리 리처의 CLR via C#
; http://www.yes24.com/24/goods/15169403

675페이지에 다음과 같은 내용이 있습니다.

CLR은 스레드가 ThreadAbortException을 발생시키도록 한다. ... 다른 예외와는 다르게 ThreadAbortException은 처리되지 않아도 응용 프로그램을 종료시키지 않는다. CLR은 이 예외를 조용히 먹어버리고 해당 스레드를 종료시킨다.


이는 다음과 같은 코드로 재현해 볼 수 있는데요.

using System;
using System.IO;
using System.Threading;

class Program
{
    static void Main(string[] args)
    {
        ThrowException();
    }

    private static void ThrowException()
    {
        EventWaitHandle ewh = new EventWaitHandle(false, EventResetMode.ManualReset);
        EventWaitHandle waitForever = new EventWaitHandle(false, EventResetMode.ManualReset);

        Thread t1 = new Thread(() =>
            {
                ewh.Set();

                try
                {
                    // throw new ApplicationException("Exception occurred!!!");
                    waitForever.WaitOne();
                } catch (Exception e)
                {
                    Console.WriteLine(e.Message);
                }
           });

        t1.Start();

        ewh.WaitOne();
        t1.Abort();
        t1.Join();
    }
}

외부 스레드에서 t1.Abort를 호출해 ThreadAbortException을 발생시켰고, 내부의 try/catch 메시지가 실행되는 걸로 확인할 수 있지만 분명히 프로그램은 종료되지 않습니다. (legacyUnhandledExceptionPolicy 속성 값이 false여도 종료가 안됩니다.)




그런데, 예외적인 상황이 하나 있습니다. 바로 ASP.NET의 "요청을 처리하는 스레드"를 외부에서 강제로 Thread.Abort 시킨 경우입니다.

재현은 ASP.NET 웹 폼 프로젝트를 만들고 다음과 같이 코딩을 하면 확인할 수 있습니다.

using System;
using System.Threading;

namespace WebApplication1
{
    public partial class Default : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {

        }

        protected void Button1_Click(object sender, EventArgs e)
        {
            Thread currentThread = Thread.CurrentThread;

            EventWaitHandle ewh = new EventWaitHandle(false, EventResetMode.ManualReset);

            Thread t1 = new Thread((objThread) =>
            {
                Thread requestThread = objThread as Thread;

                requestThread.Abort(); // Button1_Click 메서드를 실행하는 스레드를 종료,
                                       // 원치 않게 프로세스도 함께 종료됨.
                ewh.Set();
            });

            t1.Start(currentThread);
            ewh.WaitOne();
        }
    }
}

여기서 궁금한 점이 하나 생깁니다. 그렇다면 동일하게 ThreadAbortException을 발생시키는 Response.End를 호출하는 경우에는 왜 프로세스가 종료되지 않느냐는 점입니다. 이것에는 ASP.NET 내부의 배려가 있음을 Response.End 메서드를 .NET Reflector를 이용해 소스 코드를 보면 알아낼 수 있습니다.

// HttpResponse 타입의 End 메서드
public void End()
{
    if (this._context.IsInCancellablePeriod)
    {
        AbortCurrentThread();
    }
    else
    {
        this._endRequiresObservation = true;
        if (!this._flushing)
        {
            this.Flush();
            this._ended = true;
            if (this._context.ApplicationInstance != null)
            {
                this._context.ApplicationInstance.CompleteRequest();
            }
        }
    }
}

[SecurityPermission(SecurityAction.Assert, ControlThread=true)]
private static void AbortCurrentThread()
{
    Thread.CurrentThread.Abort(new HttpApplication.CancelModuleException(false));
}

보는 바와 같이, HttpApplication.CancelModuleException 타입의 인스턴스를 넘기는 차이가 있는데 정말 이것으로 인해 비정상 종료가 되지 않는지 다음과 같은 코드를 통해 확인해 볼 수 있습니다.

using System;
using System.Reflection;
using System.Runtime.Remoting;
using System.Threading;
using System.Web;

namespace WebApplication1
{
    public partial class Default : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
        }

        protected void Button1_Click(object sender, EventArgs e)
        {
            Thread currentThread = Thread.CurrentThread;
            EventWaitHandle ewh = new EventWaitHandle(false, EventResetMode.ManualReset);

            Thread t1 = new Thread((objThread) =>
            {
                Thread requestThread = objThread as Thread;

                Assembly asm = Assembly.GetAssembly(typeof(HttpApplication));
                ObjectHandle instance = AppDomain.CurrentDomain.CreateInstance(asm.FullName, "System.Web.HttpApplication+CancelModuleException", false, BindingFlags.Instance | BindingFlags.NonPublic, null, new object[] { false }, null, null);
                object objValue = instance.Unwrap();

                requestThread.Abort(objValue);
                ewh.Set();
            });

            try
            {
                t1.Start(currentThread);

                while (true)
                {
                    System.Diagnostics.Trace.WriteLine("ThreadID: " + AppDomain.GetCurrentThreadId());
                    Thread.Sleep(500);
                }
            } catch (Exception ex)
            {
                System.Diagnostics.Trace.WriteLine(ex.ToString());
            }
        }
    }
}

실행해 보면, 이번에는 해당 요청을 실행하는 스레드는 ThreadAbortException과 함께 처리를 중지하지만 w3wp.exe (또는 iisexpress.exe)는 종료하지 않고 실행이 계속됩니다.

역시, 내부를 살펴보면 System.Web.HttpApplication 타입의 ExecuteStep 메서드에서 CancelModuleException 인스턴스가 설정된 ThreadAbortException 예외에 대해서는 Thread.ResetAbort를 호출함으로써 예외 전파를 중지하고 있음을 볼 수 있습니다.

internal Exception ExecuteStep(IExecutionStep step, ref bool completedSynchronously)
{
    Exception exception = null;
    try
    {
        try
        {
            if (step.IsCancellable)
            {
                this._context.BeginCancellablePeriod();
                try
                {
                    step.Execute();
                }
                finally
                {
                    this._context.EndCancellablePeriod();
                }
                this._context.WaitForExceptionIfCancelled();
            }
            else
            {
                step.Execute();
            }
            if (!step.CompletedSynchronously)
            {
                completedSynchronously = false;
                return null;
            }
        }
        // ...[생략]...
    }
    catch (ThreadAbortException exception4)
    {
        if ((exception4.ExceptionState != null) && (exception4.ExceptionState is CancelModuleException))
        {
            if (((CancelModuleException) exception4.ExceptionState).Timeout)
            {
                exception = new HttpException(SR.GetString("Request_timed_out"), null, 0xbb9);
                PerfCounters.IncrementCounter(AppPerfCounter.REQUESTS_TIMED_OUT);
            }
            else
            {
                exception = null;
                this._stepManager.CompleteRequest();
            }
            Thread.ResetAbort();
        }
    }
    completedSynchronously = true;
    return exception;
}




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]







[최초 등록일: ]
[최종 수정일: 10/18/2015]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




... 106  107  108  109  110  [111]  112  113  114  115  116  117  118  119  120  ...
NoWriterDateCnt.TitleFile(s)
11150정성태2/21/201719351.NET Framework: 645. Visual Studio Fakes 기능에서 Shim... 클래스가 생성되지 않는 경우 [5]
11149정성태2/21/201723054오류 유형: 378. A 64-bit test cannot run in a 32-bit process. Specify platform as X64 to force test run in X64 mode on X64 machine.
11148정성태2/20/201721967.NET Framework: 644. AppDomain에 대한 단위 테스트 시 알아야 할 사항
11147정성태2/19/201721206오류 유형: 377. Windows 10에서 Fake 어셈블리를 생성하는 경우 빌드 시 The type or namespace name '...' does not exist in the namespace 컴파일 오류 발생
11146정성태2/19/201719896오류 유형: 376. Error VSP1033: The file '...' does not contain a recognized executable image. [2]
11145정성태2/16/201721349.NET Framework: 643. 작업자 프로세스(w3wp.exe)가 재시작되는 시점을 알 수 있는 방법 - 두 번째 이야기 [4]파일 다운로드1
11144정성태2/6/201724744.NET Framework: 642. C# 개발자를 위한 Win32 DLL export 함수의 호출 규약 (부록 1) - CallingConvention.StdCall, CallingConvention.Cdecl에 상관없이 왜 호출이 잘 될까요?파일 다운로드1
11143정성태2/5/201722107.NET Framework: 641. [Out] 형식의 int * 인자를 가진 함수에 대한 P/Invoke 호출 방법파일 다운로드1
11142정성태2/5/201730129.NET Framework: 640. 닷넷 - 배열 크기의 한계 [2]파일 다운로드1
11141정성태1/31/201724415.NET Framework: 639. C# 개발자를 위한 Win32 DLL export 함수의 호출 규약 (4) - CLR JIT 컴파일러의 P/Invoke 호출 규약 [1]파일 다운로드1
11140정성태1/27/201720164.NET Framework: 638. RSAParameters와 RSA파일 다운로드1
11139정성태1/22/201722883.NET Framework: 637. C# 개발자를 위한 Win32 DLL export 함수의 호출 규약 (3) - x64 환경의 __fastcall과 Name mangling [1]파일 다운로드1
11138정성태1/20/201721164VS.NET IDE: 113. 프로젝트 생성 시부터 "Enable the Visual Studio hosting process" 옵션을 끄는 방법 - 두 번째 이야기 [3]
11137정성태1/20/201719848Windows: 135. AD에 참여한 컴퓨터로 RDP 연결 시 배경 화면을 못 바꾸는 정책
11136정성태1/20/201719031오류 유형: 375. Hyper-V 내에 구성한 Active Directory 환경의 시간 구성 방법 - 두 번째 이야기
11135정성태1/20/201720038Windows: 134. Windows Server 2016의 작업 표시줄에 있는 시계가 사라졌다면? [1]
11134정성태1/20/201727443.NET Framework: 636. System.Threading.Timer를 이용해 타이머 작업을 할 때 유의할 점 [5]파일 다운로드1
11133정성태1/20/201723555.NET Framework: 635. C# 개발자를 위한 Win32 DLL export 함수의 호출 규약 (2) - x86 환경의 __fastcall [1]파일 다운로드1
11132정성태1/19/201735067.NET Framework: 634. C# 개발자를 위한 Win32 DLL export 함수의 호출 규약 (1) - x86 환경에서의 __cdecl, __stdcall에 대한 Name mangling [1]파일 다운로드1
11131정성태1/13/201723995.NET Framework: 633. C# - IL 코드 분석을 위한 팁 [2]
11130정성태1/11/201724515.NET Framework: 632. x86 실행 환경에서 SECURITY_ATTRIBUTES 구조체를 CreateEvent에 전달할 때 예외 발생파일 다운로드1
11129정성태1/11/201728893.NET Framework: 631. async/await에 대한 "There Is No Thread" 글의 부가 설명 [9]파일 다운로드1
11128정성태1/9/201723295.NET Framework: 630. C# - Interlocked.CompareExchange 사용 예제 [3]파일 다운로드1
11127정성태1/8/201722869기타: 63. (개발자를 위한) Visual Studio의 "with MSDN" 라이선스 설명
11126정성태1/7/201727589기타: 62. Edge 웹 브라우저의 즐겨찾기(Favorites)를 편집/백업/복원하는 방법 [1]파일 다운로드1
11125정성태1/7/201724446개발 환경 구성: 310. IIS - appcmd.exe를 이용해 특정 페이지에 클라이언트 측 인증서를 제출하도록 설정하는 방법
... 106  107  108  109  110  [111]  112  113  114  115  116  117  118  119  120  ...