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가 잡힙니다.
정성태

1  2  3  4  5  [6]  7  8  9  10  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
13491정성태12/19/20232272Linux: 63. 리눅스 - 다중 그룹 또는 사용자를 리소스에 권한 부여
13490정성태12/19/20232403개발 환경 구성: 698. Golang - GLIBC 의존을 없애는 정적 빌드 방법
13489정성태12/19/20232178개발 환경 구성: 697. GoLand에서 ldflags 지정 방법
13488정성태12/18/20232116오류 유형: 884. HTTP 500.0 - 명령행에서 실행한 ASP.NET Core 응용 프로그램을 실행하는 방법
13487정성태12/16/20232417개발 환경 구성: 696. C# - 리눅스용 AOT 빌드를 docker에서 수행 [1]
13486정성태12/15/20232234개발 환경 구성: 695. Nuget config 파일에 값 설정/삭제 방법
13485정성태12/15/20232125오류 유형: 883. dotnet build/restore - error : Root element is missing
13484정성태12/14/20232219개발 환경 구성: 694. Windows 디렉터리 경로를 WSL의 /mnt 포맷으로 구하는 방법
13483정성태12/14/20232364닷넷: 2184. C# - 하나의 resource 파일을 여러 프로그램에서 (AOT 시에도) 사용하는 방법파일 다운로드1
13482정성태12/13/20233046닷넷: 2183. C# - eFriend Expert OCX 예제를 .NET Core/5+ Console App에서 사용하는 방법 [2]파일 다운로드1
13481정성태12/13/20232348개발 환경 구성: 693. msbuild - .NET Core/5+ 프로젝트에서 resgen을 이용한 리소스 파일 생성 방법파일 다운로드1
13480정성태12/12/20232747개발 환경 구성: 692. Windows WSL 2 + Chrome 웹 브라우저 설치
13479정성태12/11/20232405개발 환경 구성: 691. WSL 2 (Ubuntu) + nginx 환경 설정
13477정성태12/8/20232641닷넷: 2182. C# - .NET 7부터 추가된 Int128, UInt128 [1]파일 다운로드1
13476정성태12/8/20232329닷넷: 2181. C# - .NET 8 JsonStringEnumConverter의 AOT를 위한 개선파일 다운로드1
13475정성태12/7/20232421닷넷: 2180. .NET 8 - 함수 포인터에 대한 Reflection 정보 조회파일 다운로드1
13474정성태12/6/20232238개발 환경 구성: 690. 닷넷 코어/5+ 버전의 ilasm/ildasm 실행 파일 구하는 방법 - 두 번째 이야기
13473정성태12/5/20232530닷넷: 2179. C# - 값 형식(Blittable)을 메모리 복사를 이용해 바이트 배열로 직렬화/역직렬화파일 다운로드1
13472정성태12/4/20232254C/C++: 164. Visual C++ - InterlockedCompareExchange128 사용 방법
13471정성태12/4/20232365Copilot - To enable GitHub Copilot, authorize this extension using GitHub's device flow
13470정성태12/2/20232696닷넷: 2178. C# - .NET 8부터 COM Interop에 대한 자동 소스 코드 생성 도입파일 다운로드1
13469정성태12/1/20232476닷넷: 2177. C# - (Interop DLL 없이) CoClass를 이용한 COM 개체 생성 방법파일 다운로드1
13468정성태12/1/20232354닷넷: 2176. C# - .NET Core/5+부터 달라진 RCW(Runtime Callable Wrapper) 대응 방식파일 다운로드1
13467정성태11/30/20232470오류 유형: 882. C# - Unhandled exception. System.Runtime.InteropServices.COMException (0x800080A5)파일 다운로드1
13466정성태11/29/20232631닷넷: 2175. C# - DllImport 메서드의 AOT 지원을 위한 LibraryImport 옵션
13465정성태11/28/20232374개발 환경 구성: 689. MSBuild - CopyToOutputDirectory가 "dotnet publish" 시에는 적용되지 않는 문제파일 다운로드1
1  2  3  4  5  [6]  7  8  9  10  11  12  13  14  15  ...