Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일

First-chance exception에 대해 조건에 따라 디버거가 멈추게 할 수는 없을까?

제목이 좀 난해한데요. ^^ 쉽게 이해하기 위해 예를 한번 들어볼까요?

가령 다음과 같은 코드를 작성한 후,

class Program
{
    static void Main(string[] args)
    {
        string host = "www.microsoft11111.com";
        Console.WriteLine(host + ": " + ConnectTest(host));
    }

    static bool ConnectTest(string host)
    {
        try
        {
            Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            socket.Connect(host, 80);

            socket.Close();
            return true;
        }
        catch { }

        return false;
    }
}

Visual Studio에서 "F5" 키를 눌러 디버깅을 시작하면 정상적으로 프로그램이 종료되는 것을 확인할 수 있습니다. 물론, "Output" 창에는 다음과 같은 예외 기록이 남습니다.

A first chance exception of type 'System.Net.Sockets.SocketException' occurred in System.dll

프로젝트가 커지면서 예외에 대한 try/catch를 남발하다 보면, 위와 같이 "first chance exception"이 발생하는 경우에 저 오류가 과연 아무런 영향이 없는 예외인지, 아니면 '의도치 않게 삼켜진 예외'인지 구분하기가 매우 어렵게 됩니다.

다행히, Visual Studio에서는 개발자가 try/catch로 처리했을지라도 예외가 발생한 경우 디버거로 하여금 Break가 되도록 하는 방법이 제공됩니다. 이에 대해서는 저번에 한번 설명을 드렸지요. ^^

First-Chance Exception
; https://www.sysnet.pe.kr/2/0/510

위의 마지막 부분에 설명한 데로, "Debug" / "Exceptions..." 메뉴로 "System.Net.Sockets.SocketException" 예외에 대해서 "Thrown" 상태를 설정해 두면 이후부터는 디버깅 시에 이런 예외가 발생하면 Visual Studio는 여지없이 디버거를 정지시키고 해당 예외가 발생한 라인에 멈추게 됩니다. 멋지죠? ^^




개인적으로, 저는 명시적으로 "Thrown" 설정을 해서 First-chance exception에 대한 예외를 어떻게든 없애는 방향으로 프로젝트를 진행합니다. 왜냐하면, 그렇지 않으면 나중에 프로젝트가 커져서 어떤 기능이 안될 때 도대체 왜 그 기능이 '아무런 오류도 없이 안되는지' 그 오류를 찾기 힘들기 때문입니다.

그런데, 그렇게 하다 보면 문제가 생기는 경우가 있습니다.

예를 들면, 위의 소켓 예제 코드에서 특정 서버에 대한 연결 가능 여부를 5초마다 체크하도록 했다고 가정해 보겠습니다.

ThreadPool.QueueUserWorkItem( (WaitCallback)(
    (obj) =>
        {
            while (true)
            {
                bool result = ConnectTest("www.rjrejfs.com");
                if (result == false)
                {
                    Console.WriteLine("Disconnected");
                }

                Thread.Sleep(5000);
            }
        }
    ));

만약, 위의 코드를 Visual Studio의 디버깅 상태에서 System.Net.Sockets.SocketException 예외에 대해 "Thrown" 상태로 두고 돌리면 5초마다 Visual Studio가 해당 소스 코드 라인에서 멈추는 부작용(!)이 생깁니다.

이 불편함을 없애려면 "System.Net.Sockets.SocketException" 예외의 "Thrown" 상태를 꺼야 하는데, 그렇게 되면 꼭 필요하게 체크해야 할 동일 예외에 대한 진단을 할 수 없게 됩니다.

바로 이럴 때, 특정 상황에서 발생하는 예외에 대해서는 "Thrown" 상태를 켜 두었어도 Visual Studio에서 멈추지 않게 하고 싶어지는데요. 혹시... 이에 대한 해답이 떠 오르는 분이 계실런지요? ^^

저는 한가지 생각났습니다. 바로, DebuggerNonUserCode / DebuggerStepThrough / DebuggerHidden 특성을 적절하게 사용하는 것입니다. 이를 반영해서 최종적으로 코드를 만들어 보면, 대충 다음과 같이 됩니다.

class Program
{
    static void Main(string[] args)
    {
        string host = "www.microsoft.com";
        Console.WriteLine(host + ": " + ConnectTest(host, true));

        ThreadPool.QueueUserWorkItem( (WaitCallback)(
            (obj) =>
                {
                    while (true)
                    {
                        bool result = ConnectTest("www.rjrejfs.com", false);
                        if (result == false)
                        {
                            Console.WriteLine("Disconnected");
                        }

                        Thread.Sleep(5000);
                    }
                }
            ));

        Console.ReadLine();
    }

    static bool ConnectTest(string host, bool thrown)
    {
        try
        {
            Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            if (thrown == true)
            {
                socket.Connect(host, 80);
            }
            else
            {
                if (ConnectNotThrown(socket, host, 80) == false)
                {
                    return false;
                }
            }

            socket.Close();
            return true;
        }
        catch { }

        return false;
    }

    [DebuggerHidden]
    static bool ConnectNotThrown(Socket socket, string host, int port)
    {
        try
        {
            socket.Connect(host, port);
            return true;
        }
        catch { }

        return false;
    }
}

위의 코드를 보면, 혹시나 연결이 안되었을 때 예외가 발생하기를 기대하는 코드에서는 ConnectTest(..., true)를 호출하고, 그렇지 않고 일상적으로 발생할 수 있거나 자주 발생해서 디버깅에 장애가 될 경우라면 ConnectTest(..., false)를 호출하는 것으로 해결하고 있습니다.

나름 유용하게 써 먹고 있는 팁입니다. ^^ (첨부된 파일은 위의 소스 코드가 포함된 간단한 프로젝트입니다.)

참고로, 위의 용도로는 DebuggerNonUserCode / DebuggerStepThrough / DebuggerHidden 특성 중에서 어느 것을 사용해도 무방하지만 다른 경우에서는 각각의 분리된 용도를 가지고 있는데요. 이에 대해서는 다음의 글을 참고하시면 되겠습니다. ^^

Change Debugger behavior with Attributes
; http://blogs.msdn.com/b/stevejs/archive/2005/12/03/499803.aspx






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







[최초 등록일: ]
[최종 수정일: 5/21/2021]

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

비밀번호

댓글 작성자
 



2012-06-05 03시56분
참고로, "Enable Just My Code (Managed only)" 옵션이 꺼져 있다면 위와 같은 상황에서도 Visual Studio에서 예외 발생에 대한 BP가 잡힙니다.
정성태

... 61  62  63  64  65  66  67  68  [69]  70  71  72  73  74  75  ...
NoWriterDateCnt.TitleFile(s)
12208정성태4/12/202015955Linux: 29. 리눅스 환경에서 C/C++ 프로그램이 Segmentation fault 에러가 발생한 경우
12207정성태4/2/202015751스크립트: 19. Windows PowerShell의 NonInteractive 모드
12206정성태4/2/202018405오류 유형: 613. 파일 잠금이 바로 안 풀린다면? - The process cannot access the file '...' because it is being used by another process.
12205정성태4/2/202015062스크립트: 18. Powershell에서는 cmd.exe의 명령어를 지원하진 않습니다.
12204정성태4/1/202015074스크립트: 17. Powershell 명령어에 ';' (semi-colon) 문자가 포함된 경우
12203정성태3/18/202017900오류 유형: 612. warning: 'C:\ProgramData/Git/config' has a dubious owner: '...'.
12202정성태3/18/202021171개발 환경 구성: 486. .NET Framework 프로젝트를 위한 GitLab CI/CD Runner 구성
12201정성태3/18/202018401오류 유형: 611. git-credential-manager.exe: Using credentials for username "Personal Access Token". [1]
12200정성태3/18/202018506VS.NET IDE: 145. NuGet + Github 라이브러리 디버깅 관련 옵션 3가지 - "Enable Just My Code" / "Enable Source Link support" / "Suppress JIT optimization on module load (Managed only)"
12199정성태3/17/202016146오류 유형: 610. C# - CodeDomProvider 사용 시 Unhandled Exception: System.IO.DirectoryNotFoundException: Could not find a part of the path '...\f2_6uod0.tmp'.
12198정성태3/17/202019524오류 유형: 609. SQL 서버 접속 시 "Cannot open user default database. Login failed."
12197정성태3/17/202018774VS.NET IDE: 144. .NET Core 콘솔 응용 프로그램을 배포(publish) 시 docker image 자동 생성 - 두 번째 이야기 [1]
12196정성태3/17/202015933오류 유형: 608. The ServicedComponent being invoked is not correctly configured (Use regsvcs to re-register).
12195정성태3/16/202018257.NET Framework: 902. C# - 프로세스의 모든 핸들을 열람 - 세 번째 이야기
12194정성태3/16/202020983오류 유형: 607. PostgreSQL - Npgsql.NpgsqlException: sorry, too many clients already
12193정성태3/16/202017883개발 환경 구성: 485. docker - SAP Adaptive Server Enterprise 컨테이너 실행 [1]
12192정성태3/14/202019902개발 환경 구성: 484. docker - Sybase Anywhere 16 컨테이너 실행
12191정성태3/14/202021023개발 환경 구성: 483. docker - OracleXE 컨테이너 실행 [1]
12190정성태3/14/202015585오류 유형: 606. Docker Desktop 업그레이드 시 "The process cannot access the file 'C:\Program Files\Docker\Docker\resources\dockerd.exe' because it is being used by another process."
12189정성태3/13/202021175개발 환경 구성: 482. Facebook OAuth 처리 시 상태 정보 전달 방법과 "유효한 OAuth 리디렉션 URI" 설정 규칙
12188정성태3/13/202026009Windows: 169. 부팅 시점에 실행되는 chkdsk 결과를 확인하는 방법
12187정성태3/12/202015538오류 유형: 605. NtpClient was unable to set a manual peer to use as a time source because of duplicate error on '...'.
12186정성태3/12/202017390오류 유형: 604. The SysVol Permissions for one or more GPOs on this domain controller and not in sync with the permissions for the GPOs on the Baseline domain controller.
12185정성태3/11/202017964오류 유형: 603. The browser service was unable to retrieve a list of servers from the browser master...
12184정성태3/11/202019892오류 유형: 602. Automatic certificate enrollment for local system failed (0x800706ba) The RPC server is unavailable. [3]
12183정성태3/11/202017694오류 유형: 601. Warning: DsGetDcName returned information for \\[...], when we were trying to reach [...].
... 61  62  63  64  65  66  67  68  [69]  70  71  72  73  74  75  ...