다음을 통해 공유


연습: Visual Studio에서 병렬 애플리케이션 디버깅(C#, Visual Basic, C++)

이 연습에서는 병렬 작업병렬 스택 창을 사용하여 병렬 애플리케이션을 디버그하는 방법을 보여 줍니다. 이러한 창은 TPL(작업 병렬 라이브러리) 또는 동시성 런타임을 사용하는 코드의 런타임 동작을 이해하고 확인하는 데 도움이 됩니다. 이 워크스루에서는 빌트인 중단점이 포함된 샘플 코드를 제공합니다. 코드가 중단된 후 연습에서는 병렬 작업병렬 스택 창을 사용하여 검사하는 방법을 보여 줍니다.

이 절차 설명에서는 다음 작업을 가르칩니다.

  • 한 보기에서 모든 스레드의 호출 스택을 보는 방법입니다.

  • 애플리케이션에서 생성된 System.Threading.Tasks.Task 인스턴스 목록을 보는 방법.

  • 스레드 대신 작업의 실제 호출 스택을 보는 방법입니다.

  • 병렬 작업병렬 스택 창에서 코드로 이동하는 방법입니다.

  • 창이 그룹화, 확대/축소 및 기타 관련 기능을 통해 규모 확장에 대처하는 방법

필수 조건

이 연습에서는 내 코드만 사용하도록 설정되어 있다고 가정합니다(최신 버전의 Visual Studio에서는 기본적으로 사용하도록 설정됨). 도구 메뉴에서 옵션을 선택하고, 디버깅 노드를 확장하고, 일반을 선택한 다음, 내 코드만 사용(관리 전용)을 선택합니다. 이 기능을 설정하지 않은 경우에도 이 연습을 사용할 수 있지만 결과는 그림과 다를 수 있습니다.

C# 샘플

C# 샘플을 사용하는 경우 이 연습에서는 외부 코드가 숨겨져 있다고 가정합니다. 외부 코드가 표시되는지 여부를 전환하려면 호출 스택 창의 이름 테이블 헤더를 마우스 오른쪽 버튼으로 클릭한 다음 외부 코드 표시를 선택하거나 선택 취소하십시오. 이 기능을 설정하지 않은 경우에도 이 연습을 사용할 수 있지만 결과는 그림과 다를 수 있습니다.

C++ 샘플

C++ 샘플을 사용하는 경우 이 문서에서 외부 코드에 대한 참조를 무시할 수 있습니다. 외부 코드는 C# 샘플에만 적용됩니다.

그림

이 문서의 일러스트레이션은 C# 샘플을 실행하는 쿼드 코어 컴퓨터에 기록됩니다. 다른 구성을 사용하여 이 연습을 완료할 수 있지만 그림이 컴퓨터에 표시되는 내용과 다를 수 있습니다.

샘플 프로젝트 만들기

이 연습의 샘플 코드는 아무 작업도 수행하지 않는 애플리케이션에 대한 것입니다. 연습의 목적은 도구 창을 사용하여 병렬 애플리케이션을 디버그하는 방법을 이해하는 것입니다.

  1. Visual Studio를 열고 새 프로젝트를 만듭니다.

    시작 창이 열려 있지 않으면 파일>시작 창선택합니다.

    시작 창에서 새 프로젝트를 선택합니다.

    시작 창에서 새 프로젝트 만들기을 선택합니다.

    새 프로젝트 만들기 창에서 검색 상자에 콘솔을 입력하거나 입력합니다. 다음으로 언어 목록에서 C#, C++또는 Visual Basic 을 선택한 다음 플랫폼 목록에서 Windows 를 선택합니다.

    언어 및 플랫폼 필터를 적용한 후 .NET Core 또는 C++용 콘솔 앱을 선택한 다음, 다음을 선택합니다.

    비고

    올바른 템플릿이 표시되지 않는 경우 도구>도구 및 기능 가져오기...로 이동하여 Visual Studio 설치 관리자를 엽니다. C++ 워크로드를 사용하여 .NET 데스크톱 개발 또는 데스크톱 개발을 선택한 다음 수정을 선택합니다.

    새 프로젝트 구성 창에서 이름을 입력하거나 프로젝트 이름 상자에 기본 이름을 사용합니다. 그런 다음 사용 가능한 다음 또는 만들기를 선택합니다.

    .NET Core의 경우 권장 대상 프레임워크 또는 .NET 8을 선택한 다음 만들기선택합니다.

    새 콘솔 프로젝트가 나타납니다. 프로젝트를 만든 후 원본 파일이 나타납니다.

  2. 프로젝트에서 .cpp, .cs 또는 .vb 코드 파일을 엽니다. 해당 내용을 삭제하여 빈 코드 파일을 만듭니다.

  3. 선택한 언어에 대해 다음 코드를 빈 코드 파일에 붙여넣습니다.

    using System;
    using System.Threading;
    using System.Threading.Tasks;
    using System.Diagnostics;
    
    class S
    {
      static void Main()
      {
        pcount = Environment.ProcessorCount;
        Console.WriteLine("Proc count = " + pcount);
        ThreadPool.SetMinThreads(4, -1);
        ThreadPool.SetMaxThreads(4, -1);
    
        t1 = new Task(A, 1);
        t2 = new Task(A, 2);
        t3 = new Task(A, 3);
        t4 = new Task(A, 4);
        Console.WriteLine("Starting t1 " + t1.Id.ToString());
        t1.Start();
        Console.WriteLine("Starting t2 " + t2.Id.ToString());
        t2.Start();
        Console.WriteLine("Starting t3 " + t3.Id.ToString());
        t3.Start();
        Console.WriteLine("Starting t4 " + t4.Id.ToString());
        t4.Start();
    
        Console.ReadLine();
      }
    
      static void A(object o)
      {
        B(o);
      }
      static void B(object o)
      {
        C(o);
      }
      static void C(object o)
      {
        int temp = (int)o;
    
        Interlocked.Increment(ref aa);
        while (aa < 4)
        {
          ;
        }
    
        if (temp == 1)
        {
          // BP1 - all tasks in C
          Debugger.Break();
          waitFor1 = false;
        }
        else
        {
          while (waitFor1)
          {
            ;
          }
        }
        switch (temp)
        {
          case 1:
            D(o);
            break;
          case 2:
            F(o);
            break;
          case 3:
          case 4:
            I(o);
            break;
          default:
            Debug.Assert(false, "fool");
            break;
        }
      }
      static void D(object o)
      {
        E(o);
      }
      static void E(object o)
      {
        // break here at the same time as H and K
        while (bb < 2)
        {
          ;
        }
        //BP2 - 1 in E, 2 in H, 3 in J, 4 in K
        Debugger.Break();
        Interlocked.Increment(ref bb);
    
        //after
        L(o);
      }
      static void F(object o)
      {
        G(o);
      }
      static void G(object o)
      {
        H(o);
      }
      static void H(object o)
      {
        // break here at the same time as E and K
        Interlocked.Increment(ref bb);
        Monitor.Enter(mylock);
        while (bb < 3)
        {
          ;
        }
        Monitor.Exit(mylock);
    
    
        //after
        L(o);
      }
      static void I(object o)
      {
        J(o);
      }
      static void J(object o)
      {
        int temp2 = (int)o;
    
        switch (temp2)
        {
          case 3:
            t4.Wait();
            break;
          case 4:
            K(o);
            break;
          default:
            Debug.Assert(false, "fool2");
            break;
        }
      }
      static void K(object o)
      {
        // break here at the same time as E and H
        Interlocked.Increment(ref bb);
        Monitor.Enter(mylock);
        while (bb < 3)
        {
          ;
        }
        Monitor.Exit(mylock);
    
    
        //after
        L(o);
      }
      static void L(object oo)
      {
        int temp3 = (int)oo;
    
        switch (temp3)
        {
          case 1:
            M(oo);
            break;
          case 2:
            N(oo);
            break;
          case 4:
            O(oo);
            break;
          default:
            Debug.Assert(false, "fool3");
            break;
        }
      }
      static void M(object o)
      {
        // breaks here at the same time as N and Q
        Interlocked.Increment(ref cc);
        while (cc < 3)
        {
          ;
        }
        //BP3 - 1 in M, 2 in N, 3 still in J, 4 in O, 5 in Q
        Debugger.Break();
        Interlocked.Increment(ref cc);
        while (true)
          Thread.Sleep(500); // for ever
      }
      static void N(object o)
      {
        // breaks here at the same time as M and Q
        Interlocked.Increment(ref cc);
        while (cc < 4)
        {
          ;
        }
        R(o);
      }
      static void O(object o)
      {
        Task t5 = Task.Factory.StartNew(P, TaskCreationOptions.AttachedToParent);
        t5.Wait();
        R(o);
      }
      static void P()
      {
        Console.WriteLine("t5 runs " + Task.CurrentId.ToString());
        Q();
      }
      static void Q()
      {
        // breaks here at the same time as N and M
        Interlocked.Increment(ref cc);
        while (cc < 4)
        {
          ;
        }
        // task 5 dies here freeing task 4 (its parent)
        Console.WriteLine("t5 dies " + Task.CurrentId.ToString());
        waitFor5 = false;
      }
      static void R(object o)
      {
        if ((int)o == 2)
        {
          //wait for task5 to die
          while (waitFor5) { ;}
    
    
          int i;
          //spin up all procs
          for (i = 0; i < pcount - 4; i++)
          {
            Task t = Task.Factory.StartNew(() => { while (true);});
            Console.WriteLine("Started task " + t.Id.ToString());
          }
    
          Task.Factory.StartNew(T, i + 1 + 5, TaskCreationOptions.AttachedToParent); //scheduled
          Task.Factory.StartNew(T, i + 2 + 5, TaskCreationOptions.AttachedToParent); //scheduled
          Task.Factory.StartNew(T, i + 3 + 5, TaskCreationOptions.AttachedToParent); //scheduled
          Task.Factory.StartNew(T, i + 4 + 5, TaskCreationOptions.AttachedToParent); //scheduled
          Task.Factory.StartNew(T, (i + 5 + 5).ToString(), TaskCreationOptions.AttachedToParent); //scheduled
    
          //BP4 - 1 in M, 2 in R, 3 in J, 4 in R, 5 died
          Debugger.Break();
        }
        else
        {
          Debug.Assert((int)o == 4);
          t3.Wait();
        }
      }
      static void T(object o)
      {
        Console.WriteLine("Scheduled run " + Task.CurrentId.ToString());
      }
      static Task t1, t2, t3, t4;
      static int aa = 0;
      static int bb = 0;
      static int cc = 0;
      static bool waitFor1 = true;
      static bool waitFor5 = true;
      static int pcount;
      static S mylock = new S();
    }
    

코드 파일을 업데이트한 후 변경 내용을 저장하고 솔루션을 빌드합니다.

  1. 파일 메뉴에서 모두 저장을 선택합니다.

  2. 빌드 메뉴에서 솔루션 다시 빌드를 선택합니다.

C++ 샘플에서는 네 번의 호출 Debugger.BreakDebugBreak 이 있습니다. 따라서 중단점을 삽입할 필요가 없습니다. 애플리케이션을 단순히 실행하는 것만으로도 디버거에서 최대 4번까지 오류가 발생하여 중단됩니다.

병렬 스택 창 사용: 스레드 뷰

시작하려면 디버그 메뉴에서 디버깅 시작을 선택합니다. 첫 번째 중단점이 적중될 때까지 기다립니다.

단일 스레드의 호출 스택 보기

  1. 디버그 메뉴에서 Windows를 가리킨 다음 스레드를 선택합니다. Visual Studio 아래쪽에 스레드 창을 도킹합니다.

  2. 디버그 메뉴에서 Windows를 가리킨 다음 호출 스택을 선택합니다. 호출 스택 창을 아래쪽 Visual Studio에 도킹합니다.

  3. 스레드 창에서 스레드를 두 번 클릭하여 현재 상태로 만듭니다. 현재 스레드에는 노란색 화살표가 있습니다. 현재 스레드를 변경하면 호출 스택 창에 해당 호출 스택 이 표시됩니다.

병렬 스택 창 검사

디버그 메뉴에서 Windows를 가리킨 다음 병렬 스택을 선택합니다. 왼쪽 위 모서리의 상자에서 스레드 가 선택되어 있는지 확인합니다.

병렬 스택 창을 사용하면 한 보기에서 동시에 여러 호출 스택을 볼 수 있습니다. 다음 그림에서는 호출 스택 창 위의 병렬 스택 창을 보여 줍니다.

병렬 스택 창의 스레드 보기 스크린샷

병렬 스택 창의 스레드 뷰

주 스레드의 호출 스택이 한 상자에 나타나고 다른 4개의 스레드에 대한 호출 스택이 다른 상자에 그룹화됩니다. 스택 프레임이 동일한 메서드 컨텍스트를 공유하기 때문에 네 개의 스레드가 함께 그룹화됩니다. 즉, 동일한 메서드에 있습니다. A, BC. 동일한 상자를 공유하는 스레드 ID 및 스레드 이름을 보려면 헤더([#] 스레드)가 있는 상자 위로 마우스를 가져옵니다. 현재 스레드는 굵게 표시됩니다.

스레드 ID 및 이름을 보여 주는 도구 설명의 스크린샷

스레드 ID 및 이름을 보여주는 PDB_Walkthrough_1A 도구 설명

노란색 화살표는 현재 스레드의 활성 스택 프레임을 나타냅니다.

호출 스택 창을 마우스 오른쪽 단추로 클릭하여 스택 프레임(모듈 이름, 매개 변수 형식, 매개 변수 이름, 매개 변수 값, 줄 번호바이트 오프셋)에 대해 표시할 세부 정보를 설정할 수 있습니다.

상자 주위의 파란색 강조 표시는 현재 스레드가 해당 상자의 일부임을 나타냅니다. 현재 스레드는 도구 설명의 굵은 스택 프레임으로도 표시됩니다. 스레드 창에서 주 스레드를 두 번 클릭하면 병렬 스택 창의 강조 표시 화살표가 그에 따라 이동하는 것을 확인할 수 있습니다.

병렬 스택 창에서 강조 표시된 주 스레드의 스크린샷

병렬 스택 창에서 강조 표시된 주 스레드

두 번째 중단점까지 실행 다시 시작

두 번째 중단점에 도달할 때까지 실행을 다시 시작하려면 디버그 메뉴에서 [계속]을 선택합니다. 다음 그림에서는 두 번째 중단점의 스레드 트리를 보여 줍니다.

여러 분기를 보여 주는 병렬 스택 창의 스크린샷

여러 분기들을 보여주는 병렬 스택 창 PDB_Walkthrough_2

첫 번째 중단점에서는 4개의 스레드가 모두 S.A에서 S.B 메서드로 이동했습니다. 해당 정보는 병렬 스택 창에 계속 표시되지만 4개의 스레드가 더 진행되었습니다. 그 중 한 명은 계속 S.D로 갔고, 그 다음 S.E로 갔습니다. 다른 한 명은 S.F, S.G, S.H로 계속 갔습니다. 다른 두 명은 S.I와 S.J로 계속 갔으며, 그 중 한 명은 S.K로 갔고 다른 하나는 외부 코드 (비사용자)로 계속 진행되었습니다.

스택 프레임을 마우스로 가리키면 스레드 ID와 다른 프레임 세부 정보를 볼 수 있습니다. 파란색 강조 표시는 현재 스레드를 나타내고 노란색 화살표는 현재 스레드의 활성 스택 프레임을 나타냅니다.

상자 헤더(예: 스레드 1 개 또는 스레드 2개)를 마우스로 가리키면 스레드의 스레드 ID를 볼 수 있습니다. 스택 프레임을 마우스로 가리키면 스레드 ID와 다른 프레임 세부 정보를 볼 수 있습니다. 파란색 강조 표시는 현재 스레드를 나타내고 노란색 화살표는 현재 스레드의 활성 스택 프레임을 나타냅니다.

천 스레드 아이콘(겹쳐진 선)은 비전류 스레드의 활성 스택 프레임을 나타냅니다. 호출 스택 창에서 S.B를 두 번 클릭하여 프레임을 전환합니다. 병렬 스택 창은 곡선 화살표 아이콘을 사용하여 현재 스레드의 현재 스택 프레임을 나타냅니다.

비고

병렬 스택 창의 모든 아이콘에 대한 설명은 병렬 스택 창 사용을 참조하세요.

스레드 창에서 스레드 간을 전환하고 병렬 스택 창의 보기가 업데이트되는 것을 관찰합니다.

병렬 스택 창의 바로 가기 메뉴를 사용하여 다른 스레드 또는 다른 스레드의 다른 프레임으로 전환할 수 있습니다. 예를 들어 S.J에서 마우스 오른쪽 버튼을 클릭하고 프레임 전환을 가리킨 다음 명령을 선택합니다.

병렬 스택 실행 경로의 스크린샷

병렬 스택 실행 경로

S.C.를 마우스 오른쪽 단추로 클릭하고 프레임으로 전환을 가리킵니다. 명령 중 하나에는 현재 스레드의 스택 프레임을 나타내는 확인 표시가 있습니다. 동일한 스레드의 해당 프레임으로 전환하거나(곡선 화살표만 이동) 다른 스레드로 전환할 수 있습니다(파란색 강조 표시도 이동). 다음 그림에서는 하위 메뉴에 대해 설명합니다.

J가 현재인 동안 C에서 2개의 옵션이 있는 스택 메뉴의 스크린샷.

현재 J에서 2개의 옵션이 있는 C의 스택 메뉴

메서드 컨텍스트가 하나의 스택 프레임에만 연결되면 상자 머리글에 1개의 스레드 가 표시되고 두 번 클릭하여 전환할 수 있습니다. 연결된 프레임이 1개 이상인 메서드 컨텍스트를 두 번 클릭하면 메뉴가 자동으로 나타납니다. 메서드 컨텍스트를 마우스로 가리키면 오른쪽에 검은색 삼각형이 표시됩니다. 이 삼각형을 클릭하면 바로 가기 메뉴도 표시됩니다.

스레드가 많은 대규모 애플리케이션의 경우 스레드의 하위 집합에만 집중할 수 있습니다. 병렬 스택 창에는 플래그가 지정된 스레드에 대해서만 호출 스택이 표시될 수 있습니다. 스레드에 플래그를 지정하려면 바로 가기 메뉴 또는 스레드의 첫 번째 셀을 사용합니다.

도구 모음에서 목록 상자 옆에 있는 플래그만 표시 단추를 선택합니다.

병렬 스택 창 및 도구 설명의 스크린샷

병렬 스택 창 및 도구 설명

이제 플래그가 지정된 스레드만 병렬 스택 창에 표시됩니다.

세 번째 중단점까지 실행 다시 시작

  1. 세 번째 중단점에 도달할 때까지 실행을 다시 시작하려면 디버그 메뉴에서 [계속]을 선택합니다.

    여러 스레드가 동일한 메서드에 있지만 메서드가 호출 스택의 시작 부분에 없는 경우 메서드는 다른 상자에 나타납니다. 현재 중단점의 예는 세 개의 스레드가 있고 세 개의 상자에 나타나는 S.L입니다. S.L을 두 번 클릭합니다.

    병렬 스택 창의 실행 경로 스크린샷

    병렬 스택 창의 실행 경로

    다른 두 상자에서 S.L이 굵게 표시되어 있어 다른 곳에서 어떻게 나타나는지 볼 수 있습니다. S.L로 호출되는 프레임과 호출하는 프레임을 확인하려면 도구 모음에서 메서드 보기 설정/해제 단추를 선택합니다. 다음 그림에서는 병렬 스택 창의 메서드 뷰를 보여 줍니다.

    병렬 스택 창의 메서드 보기 스크린샷

    병렬 스택 창의 메서드 뷰

    다이어그램이 선택한 메서드를 중심으로 회전하여 보기에 있는 중간 자기 상자에 배치되는 것을 확인하세요. 수신자와 발신자는 각각 위쪽과 아래쪽에 표시됩니다. 메서드 보기 설정/해제 단추를 다시 선택하여 이 모드를 그대로 둡니다.

    병렬 스택 창의 바로 가기 메뉴에는 다음과 같은 다른 항목도 있습니다.

    • 16진수 표시 는 도구 설명의 숫자를 10진수와 16진수 사이로 전환합니다.

    • 기호 설정 은 해당 대화 상자를 엽니다.

    • 소스의 스레드 표시 는 소스 코드에서 스레드 마커의 표시를 토글합니다. 소스 코드에서 스레드의 위치를 보여 줍니다.

    • 외부 코드 표시 는 사용자 코드에 없는 경우에도 모든 프레임을 표시합니다. 다른 프레임에 맞게 다이어그램이 확장되는지 확인합니다(기호가 없으므로 흐리게 표시될 수 있습니다).

  2. 병렬 스택 창에서 도구 모음의 현재 스택 프레임으로 자동 스크롤 단추가 있는지 확인합니다.

    큰 다이어그램이 있고 다음 중단점으로 이동하면 보기가 현재 스레드의 활성 스택 프레임으로 자동 스크롤되도록 할 수 있습니다. 즉, 중단점에 먼저 도달한 스레드입니다.

  3. 계속하기 전에 병렬 스택 창에서 왼쪽으로 끝까지 아래로 스크롤합니다.

네 번째 중단점까지 실행 다시 시작

  1. 네 번째 중단점에 도달할 때까지 실행을 다시 시작하려면 디버그 메뉴에서 [계속]을 선택합니다.

    화면이 자동으로 스크롤되는 방식을 주목하세요. 스레드 창에서 스레드를 전환하거나 호출 스택 창에서 스택 프레임을 전환할 때, 보기가 항상 올바른 프레임으로 자동으로 스크롤되는 것을 확인합니다. 현재 도구 프레임으로 자동 스크롤 옵션을 끄고 차이점을 확인합니다.

    새의 눈으로 보기병렬 스택 창의 큰 다이어그램에도 도움이 됩니다. 기본적으로 새의 눈 전망이 켜져 있습니다. 그러나 다음 그림과 같이 창의 오른쪽 아래 모서리에 있는 스크롤 막대 사이의 단추를 클릭하여 전환할 수 있습니다.

    병렬 스택 창의 조감도 스크린샷

    병렬 스택 창의 조감도

    조감도에서 직사각형을 이동하여 다이어그램 주위를 빠르게 이동할 수 있습니다.

    다이어그램을 원하는 방향으로 이동하는 또 다른 방법은 다이어그램의 빈 영역을 선택하고 원하는 위치로 끌어서 놓는 것입니다.

    다이어그램을 확대/축소하려면 Ctrl 키를 누른 채 마우스 휠을 이동합니다. 또는 도구 모음에서 확대/축소 단추를 선택한 다음 확대/축소 도구를 사용합니다.

    도구 메뉴를 클릭하고 옵션을 클릭한 다음 디버깅 노드에서 옵션을 선택하거나 선택 취소하여 아래쪽이 아닌 하향식 방향으로 스택을 볼 수도 있습니다.

  2. 계속하기 전에 디버그 메뉴에서 디버깅 중지를 선택하여 실행을 종료합니다.

병렬 작업 창 및 병렬 스택 창의 작업 보기 사용

계속하기 전에 이전 절차를 완료하는 것이 좋습니다.

첫 번째 중단점에 도달할 때까지 애플리케이션을 다시 시작합니다.

  1. 디버그 메뉴에서 디버깅 시작을 선택하고 첫 번째 중단점이 적중될 때까지 기다립니다.

  2. 디버그 메뉴에서 Windows를 가리킨 다음 스레드를 선택합니다. Visual Studio 아래쪽에 스레드 창을 도킹합니다.

  3. 디버그 메뉴에서 Windows를 가리키고 호출 스택을 선택합니다. Visual Studio 아래쪽에 호출 스택 창을 도킹합니다.

  4. 스레드 창에서 스레드를 두 번 클릭하여 현재 상태로 만듭니다. 현재 스레드에는 노란색 화살표가 있습니다. 현재 스레드를 변경하면 다른 창이 업데이트됩니다. 다음으로 작업을 검토합니다.

  5. 디버그 메뉴에서 Windows를 가리킨 다음 작업을 선택합니다. 다음 그림에서는 작업 창을 보여 줍니다.

    작업 창에서 실행 중인 네 개의 작업의 스크린샷

    작업 창 PDW_Walkthrough_6 네 개의 실행 중인 작업

    실행 중인 각 태스크에 대해 동일한 이름의 속성으로 반환되는 ID, 이를 실행하는 스레드의 ID 및 이름, 그리고 해당 위치(마우스를 올리면 전체 호출 스택을 보여주는 도구 설명이 나타나는 위치)를 읽을 수 있습니다. 또한 작업 열 아래에서 작업에 전달된 메서드를 볼 수 있습니다. 즉, 시작점입니다.

    열을 정렬할 수 있습니다. 정렬 열과 방향을 나타내는 정렬 문자 모양을 확인합니다. 열을 왼쪽이나 오른쪽으로 드래그하여 순서를 변경할 수도 있습니다.

    노란색 화살표는 현재 작업을 나타냅니다. 작업을 두 번 클릭하거나 바로 가기 메뉴를 사용하여 작업을 전환할 수 있습니다. 작업을 전환하면 기본 스레드가 현재 상태가 되고 다른 창이 업데이트됩니다.

    한 작업에서 다른 작업으로 수동으로 전환하면 화살표 윤곽선은 비전류 작업에 대한 현재 디버거 컨텍스트를 나타냅니다.

    한 작업에서 다른 작업으로 수동으로 전환하면 노란색 화살표가 이동하지만 흰색 화살표는 디버거가 중단된 작업을 계속 표시합니다.

두 번째 중단점까지 실행 다시 시작

두 번째 중단점에 도달할 때까지 실행을 다시 시작하려면 디버그 메뉴에서 [계속]을 선택합니다.

이전에는 상태 열에 모든 작업이 활성으로 표시되었지만 이제 두 작업이 차단되었습니다. 여러 가지 이유로 작업을 차단할 수 있습니다. 상태 열에서 대기 중인 작업 위로 마우스를 가져가서 차단된 이유를 알아봅니다. 예를 들어 다음 그림에서 작업 11은 작업 12에서 대기 중입니다.

작업 창에서 대기 중인 두 작업의 스크린샷.

이전에는 상태 열에 모든 작업이 활성으로 표시되었지만 이제 두 작업이 차단되었습니다. 여러 가지 이유로 작업을 차단할 수 있습니다. 상태 열에서 대기 중인 작업 위로 마우스를 가져가서 차단된 이유를 알아봅니다. 예를 들어 다음 그림에서 작업 4는 작업 5에서 대기 중입니다.

작업 창에서 두 개의 대기 작업

작업 4는 작업 2에 할당된 스레드가 소유한 모니터에서 대기 중입니다. 머리글 행을 마우스 오른쪽 버튼으로 클릭하고 및 >을 선택하여 작업 2의 스레드 할당 값을 확인합니다.

작업 창에서 대기 중인 작업 및 도구 설명

작업 창의 첫 번째 열에서 플래그를 클릭하여 작업에 플래그를 지정할 수 있습니다 .

플래그 지정을 사용하여 동일한 디버깅 세션의 여러 중단점 간에 작업을 추적하거나 호출 스택이 병렬 스택 창에 표시되는 작업을 필터링할 수 있습니다.

이전에 병렬 스택 창을 사용했을 때 애플리케이션 스레드를 봤습니다. 병렬 스택 창을 다시 볼 수 있지만 이번에는 애플리케이션 작업을 봅니다. 왼쪽 위에 있는 상자에서 작업을 선택하여 이 작업을 수행합니다. 다음 그림에서는 작업 보기를 보여 줍니다.

병렬 스택 창의 작업 보기 스크린샷

병렬 스택 창의 작업 보기

현재 태스크를 실행하지 않는 스레드는 병렬 스택 창의 작업 보기에 표시되지 않습니다. 또한 작업을 실행하는 스레드의 경우 태스크와 관련이 없는 스택 프레임 중 일부는 스택의 위쪽과 아래쪽에서 필터링됩니다.

작업 창을 다시 봅니다. 열 머리글을 마우스 오른쪽 단추로 클릭하여 열의 바로 가기 메뉴를 표시합니다.

바로 가기 메뉴를 사용하여 열을 추가하거나 제거할 수 있습니다. 예를 들어 AppDomain 열은 선택되지 않습니다. 따라서 목록에 표시되지 않습니다. 부모를 선택합니다. 부모 열은 네 가지 작업 중 어느 것에 대한 값도 없이 나타납니다.

세 번째 중단점까지 실행 다시 시작

세 번째 중단점에 도달할 때까지 실행을 다시 시작하려면 디버그 메뉴에서 [계속]을 선택합니다.

작업 창의 부모-자식 보기 스크린샷

이 예제 실행에서는 작업 11과 작업 12가 동일한 스레드에서 실행되고 있음을 확인합니다(숨겨진 경우 스레드 할당 열 표시). 이 정보는 스레드 창에 표시되지 않습니다 . 여기에서 보는 것은 작업 창의 또 다른 이점입니다. 이를 확인하려면 병렬 스택 창을 봅니다. 작업을 보고 있는지 확인 합니다. 병렬 스택 창에서 도구 설명을 검사하여 작업 11 및 12를 찾을 수 있습니다.

병렬 스택 창의 작업 보기

이제 새 작업인 작업 5가 실행 중이며 작업 4가 대기 중입니다. 상태 창에서 대기 중인 작업을 마우스로 가리키면 이유를 확인할 수 있습니다. 부모 열에서 작업 4가 작업 5의 부모임을 확인합니다.

부모-자식 관계를 더 잘 시각화하려면 열 머리글 행을 마우스 오른쪽 단추로 클릭한 다음 부모 자식 보기를 선택합니다. 다음 그림이 표시됩니다.

작업 창의 부모-자식 보기 PDB_Walkthrough_9

작업 4와 작업 5가 동일한 스레드에서 실행되고 있습니다(숨겨진 경우 스레드 할당 열 표시). 이 정보는 스레드 창에 표시되지 않습니다 . 여기에서 보는 것은 작업 창의 또 다른 이점입니다. 이를 확인하려면 병렬 스택 창을 봅니다. 작업을 보고 있는지 확인 합니다. 작업 창에서 두 번 클릭하여 작업 4 및 5를 찾습니다. 이렇게 하면 병렬 스택 창의 파란색 강조 표시가 업데이트됩니다. 병렬 스택 창에서 도구 설명을 검사하여 작업 4 및 5를 찾을 수도 있습니다.

병렬 스택 창의 작업 보기

병렬 스택 창에서 S.P를 마우스 오른쪽 단추로 클릭한 다음 스레드로 이동을 선택합니다. 창이 스레드 뷰로 전환되고 해당 프레임이 보기에 있습니다. 동일한 스레드에서 두 작업을 모두 볼 수 있습니다.

스레드 보기에서 강조된 스레드 PDB_Walkthrough_9B

이는 스레드 창과 비교하여 병렬 스택 창의 작업 보기의 또 다른 이점 입니다 .

네 번째 중단점까지 실행 다시 시작

세 번째 중단점에 도달할 때까지 실행을 다시 시작하려면 디버그 메뉴에서 [계속]을 선택합니다. ID를 기준으로 정렬할 ID 열 머리글을 선택합니다. 다음 그림이 표시됩니다.

병렬 스택 창의 네 가지 작업 상태 스크린샷

이제 작업 10과 작업 11이 서로 대기 중이며 차단됩니다. 현재 예약된 몇 가지 새 작업도 있습니다. 예약된 작업은 코드에서 시작되었지만 아직 실행되지 않은 작업입니다. 따라서 위치스레드 할당 열에 기본 메시지가 표시되거나 비어 있습니다.

병렬 스택 창의 4개 작업 상태

작업 5가 완료되었으므로 더 이상 표시되지 않습니다. 컴퓨터에서 그렇지 않고 교착 상태가 표시되지 않는 경우 F11 키를 눌러 한 번에 한 단계씩 실행합니다.

이제 작업 3과 작업 4가 서로 대기 중이며 차단됩니다. 작업 2에 속하며, 현재 일정에 포함된 5가지 새 작업이 있습니다. 예약된 작업은 코드에서 시작되었지만 아직 실행되지 않은 작업입니다. 따라서 위치스레드 할당 열은 비어 있습니다.

병렬 스택 창을 다시 봅니다. 각 상자의 헤더에는 스레드 ID 및 이름을 보여 주는 도구 설명이 있습니다. 병렬 스택 창에서 작업 보기로 전환합니다. 다음 그림과 같이 머리글을 마우스로 가리키면 작업 ID와 이름 및 작업의 상태를 볼 수 있습니다.

병렬 스택 창의 헤더 도구 설명

작업을 열별로 그룹화할 수 있습니다. 작업 창에서 상태 열 머리글을 마우스 오른쪽 단추로 클릭한 다음 상태별로 그룹을 선택합니다. 다음 그림에서는 상태별로 그룹화된 작업 창을 보여 줍니다.

작업 창의 그룹화된 작업 스크린샷

작업 창의 그룹화된 작업

다른 열을 그룹화할 수도 있습니다. 작업을 그룹화하여 작업의 하위 집합에 집중할 수 있습니다. 축소 가능한 각 그룹은 함께 그룹화된 항목의 수를 포함하고 있습니다.

검사할 작업 창의 마지막 기능은 작업을 마우스 오른쪽 단추로 클릭할 때 표시되는 바로 가기 메뉴입니다.

바로 가기 메뉴는 작업의 상태에 따라 다른 명령을 표시합니다. 이 명령에는 복사, 모두 선택, 16진수 표시, 작업으로 전환, 할당된 스레드 고정, 이 스레드를 제외한 모든 스레드 고정, 할당된 스레드 해동플래그가 포함될 수 있습니다.

작업 또는 태스크의 기본 스레드를 고정하거나 할당된 스레드를 제외한 모든 스레드를 고정할 수 있습니다. 작업 창에서 고정 스레드는 스레드 창에서처럼 파란색 일시 중지 아이콘으로 표시됩니다.

요약

이 연습에서는 병렬 작업병렬 스택 디버거 창을 보여 줍니다. 다중 스레드 코드를 사용하는 실제 프로젝트에서 이러한 창을 사용합니다. C++, C#또는 Visual Basic으로 작성된 병렬 코드를 검사할 수 있습니다.