연습: 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 설치 관리자가 열립니다. .NET 데스크톱 개발 또는 C++를 사용한 데스크톱 개발 워크로드를 선택한 다음, 수정을 선택합니다.

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

    .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. 빌드 메뉴에서 솔루션 다시 빌드를 선택합니다.

Debugger.Break가 4번 호출됩니다(C++ 샘플의 경우 DebugBreak). 따라서 중단점을 삽입할 필요가 없습니다. 애플리케이션을 실행하기만 하면 디버거에서 애플리케이션이 최대 4번 중단됩니다.

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

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

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

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

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

  3. 스레드 창에서 스레드를 두 번 클릭하여 활성화합니다. 활성화된 현재 스레드에는 노란색 화살표가 표시됩니다. 현재 스레드를 변경하는 경우 해당 호출 스택이 호출 스택 창에 표시됩니다.

병렬 스택 창 검사

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

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

Screenshot of Threads view in Parallel Stacks window.

Threads view in Parallel Stacks window

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

Screenshot of Tooltip that shows thread IDs and names.

Tooltip that shows thread IDs and names

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

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

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

Screenshot of Highlighted main thread in Parallel Stacks window.

Highlighted main thread in Parallel Stacks window

2번째 중단점까지 실행 계속

2번째 중단점이 적중될 때까지 실행을 계속하려면 디버그 메뉴에서 계속을 선택합니다. 다음 그림에서는 2번째 중단점에서의 스레드 트리를 보여 줍니다.

Screenshot of Parallel Stacks window that shows many branches.

Parallel Stacks window that shows many branches

첫 번째 중단점에서 4개 스레드가 모두 S.A 메서드에서 S.B 메서드로 이동되고 다시 S.C 메서드로 이동되었습니다. 해당 정보가 병렬 스택 창에 계속 표시되지만 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를 마우스 오른쪽 단추로 클릭하고 프레임으로 전환을 가리킨 다음, 명령을 선택합니다.

Screenshot of Parallel Stacks Path of Execution.

Parallel Stacks Path of Execution

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

Screenshot of Stacks menu with 2 options on C while J is current.

Stacks menu with 2 options on C while J is current

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

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

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

Screenshot of Parallel Stacks window and tooltip.

Parallel Stacks window and tooltip

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

3번째 중단점까지 실행 계속

  1. 3번째 중단점이 적중될 때까지 실행을 계속하려면 디버그 메뉴에서 계속을 선택합니다.

    여러 스레드가 동일한 메서드에 있지만 메서드가 호출 스택의 시작 지점에 있지 않은 경우 메서드가 서로 다른 상자에 나타납니다. 현재 중단점의 예는 S.L입니다. 이 중단점은 3개의 스레드를 포함하며 3개의 상자에 나타납니다. S.L을 두 번 클릭합니다.

    Screenshot of Execution path in Parallel Stacks window.

    Execution path in Parallel Stacks window

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

    Screenshot of Method view in Parallel Stacks window.

    Method view in Parallel Stacks window

    다이어그램이 선택한 메서드를 축으로 회전하고 뷰 중간에 있는 자체 상자에 배치됩니다. 호출 수신자와 호출자가 맨 위와 맨 아래에 각각 나타납니다. 메서드 보기 설정/해제 단추를 다시 선택하여 이 모드를 종료합니다.

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

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

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

    • 소스의 스레드 표시 소스 코드에서 스레드 마커(소스 코드에서 스레드의 위치를 표시)의 표시를 설정/해제합니다.

    • 외부 코드 표시는 사용자 코드에 없는 프레임을 비롯한 모든 프레임을 표시합니다. 이 메뉴 항목을 사용하면 다른 프레임을 수용할 수 있게 다이어그램이 확장됩니다. 다른 프레임에 대한 기호가 없어서 프레임이 흐리게 표시될 수 있습니다.

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

    큰 다이어그램이 있는 경우 다음 중단점으로 한 단계씩 코드를 실행할 때 뷰가 현재 스레드의 활성 스택 프레임(중단점을 처음 적중하는 스레드)으로 자동 스크롤되게 할 수 있습니다.

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

4번째 중단점까지 실행 계속

  1. 4번째 중단점이 적중될 때까지 실행을 계속하려면 디버그 메뉴에서 계속을 선택합니다.

    뷰가 어떻게 자동 스크롤되는지 봅니다. 스레드 창에서 스레드를 전환하거나 호출 스택 창에서 스택 프레임을 전환할 때도 보기가 항상 정확한 프레임으로 자동 스크롤됩니다. 현재 도구 프레임으로 자동 스크롤 옵션을 끄고 차이를 확인합니다.

    부감 보기병렬 스택 창에서 큰 다이어그램을 사용할 때 도움이 됩니다. 기본적으로 부감 보기가 설정됩니다. 그러나 다음 그림과 같이 창의 오른쪽 맨 아래에 있는 스크롤 막대 사이의 단추를 클릭하여 이 보기를 설정/해제할 수 있습니다.

    Screenshot of Birds eye view in Parallel Stacks window.

    Bird's-eye view in Parallel Stacks window

    부감 보기에서 사각형을 다이어그램 주변에서 빠르게 이동할 수 있습니다.

    사각형을 한 방향으로 이동하는 또 한 가지 방법은 다이어그램의 검은색 영역을 선택하여 원하는 위치로 끄는 것입니다.

    다이어그램을 확대하거나 축소하려면 Ctrl 키를 누른 상태에서 마우스 휠을 움직입니다. 또는 도구 모음의 확대/축소 단추를 선택하고 확대/축소 도구를 사용합니다.

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

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

병렬 작업 창과 병렬 스택 창의 작업 뷰 사용

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

첫 번째 중단점이 적중될 때까지 애플리케이션을 다시 시작하려면:

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

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

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

  4. 스레드 창에서 스레드를 두 번 클릭하여 활성화합니다. 활성화된 현재 스레드에는 노란색 화살표가 표시됩니다. 현재 스레드를 변경하면 다른 창이 업데이트됩니다. 다음에는 작업을 살펴보겠습니다.

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

    Screenshot of Four running tasks in Tasks window.

    Four running tasks in Tasks window

    실행 중인 각 작업에 대해 같은 이름의 속성에서 반환되는 작업의 ID, 작업을 실행하는 스레드의 ID 및 이름, 작업의 위치가 표시됩니다. 작업을 가리키면 전체 호출 스택이 포함된 도구 설명이 표시됩니다. 또한 작업 열 아래에서 작업에 전달된 메서드 즉, 시작점을 볼 수 있습니다.

    열을 정렬할 수 있습니다. 정렬 문자 모양이 정렬 열과 방향을 나타냅니다. 열을 왼쪽이나 오른쪽으로 끌어서 열을 다시 정렬할 수도 있습니다.

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

    작업 간을 수동으로 전환하는 경우 화살표 윤곽선은 현재가 아닌 작업에 대한 현재 디버거 컨텍스트를 나타냅니다.

    작업 간을 수동으로 전환하는 경우 노란색 화살표는 이동하지만 흰색 화살표는 디버거를 중단시킨 작업을 계속 표시합니다.

2번째 중단점까지 실행 계속

2번째 중단점이 적중될 때까지 실행을 계속하려면 디버그 메뉴에서 계속을 선택합니다.

이전에는 상태 열에 모든 작업이 활성으로 표시되었지만 이제는 작업 중 두 개가 차단됨입니다. 작업은 다양한 이유로 차단될 수 있습니다. 상태 열에서 대기 중인 작업을 가리키면 해당 작업이 차단된 이유를 알 수 있습니다. 예를 들어, 다음 그림에서 작업 11이 작업 12를 기다리고 있습니다.

Screenshot of Two waiting tasks in Tasks window.

이전에는 상태 열에 모든 작업이 활성으로 표시되었지만 이제는 작업 중 두 개가 차단됨입니다. 작업은 다양한 이유로 차단될 수 있습니다. 상태 열에서 대기 중인 작업을 가리키면 해당 작업이 차단된 이유를 알 수 있습니다. 예를 들어, 다음 그림에서 작업 4 작업 5를 기다리고 있습니다.

Two waiting tasks in Tasks window

작업 4는 작업 2에 할당된 스레드가 소유하는 모니터를 기다리고 있습니다. (헤더 행을 마우스 오른쪽 단추로 클릭하고 >스레드 할당을 선택하여 작업 2에 대한 스레드 할당 값을 확인합니다.)

Waiting task and tooltip in Tasks window

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

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

앞에서 병렬 스택 창을 사용할 때 애플리케이션 스레드를 보았습니다. 이번에는 병렬 작업 창에서 애플리케이션 작업을 봅니다. 이렇게 하려면 왼쪽 위의 상자에서 작업을 선택합니다. 다음 그림에서는 작업 뷰를 보여 줍니다.

Screenshot of Tasks view in Parallel Stacks window.

Tasks view in Parallel Stacks window

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

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

바로 가기 메뉴를 사용하여 열을 추가하거나 제거할 수 있습니다. 예를 들어, AppDomain 열은 선택되지 않았으므로 목록에 표시되지 않습니다. 부모 선택 4개 작업 중 하나에 대해 부모 열이 값 없이 표시됩니다.

3번째 중단점까지 실행 계속

3번째 중단점이 적중될 때까지 실행을 계속하려면 디버그 메뉴에서 계속을 선택합니다.

Screenshot of Parent-child view in Tasks window.

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

Task view in Parallel Stacks window

지금 새 작업인 작업 5가 실행 중이며 작업 4가 대기 중입니다. 상태 창에서 대기 중인 작업을 가리키면 해당 작업이 대기 중인 이유를 알 수 있습니다. 부모 열에서 작업 4는 작업 5의 부모입니다.

부모-자식 관계를 시각적으로 표현하려면 부모 열 헤더 행을 오른쪽 단추로 클릭하고 부모 자식 뷰를 선택합니다. 다음 그림이 표시됩니다.

Parent-child view in Tasks window

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

Task view in Parallel Stacks window

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

Highlighted thread in threads view

이는 스레드 창과 비교하여 병렬 스택 창의 작업 보기를 사용할 때의 또 한 가지 이점입니다.

4번째 중단점까지 실행 계속

3번째 중단점이 적중될 때까지 실행을 계속하려면 디버그 메뉴에서 계속을 선택합니다. ID 열 헤더를 선택하여 ID순으로 정렬합니다. 다음 그림이 표시됩니다.

Screenshot of Four task states in Parallel Stacks window.

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

Four task states in Parallel Stacks window

작업 5가 완료되어 더 이상 표시되지 않습니다. 그렇지 않고 교착 상태도 표시되지 않으면 F11 키를 눌러 한 단계씩 코드를 실행합니다.

작업 3과 작업 4가 서로 대기 중이고 차단 상태에 있습니다. 또한 작업 2의 자식인 5개의 새 작업이 있고 현재 예약되어 있습니다. 예약된 작업은 코드 내에서는 시작되었지만 아직 실행되지 않은 작업입니다. 따라서 위치스레드 할당 열이 비어 있습니다.

병렬 스택 창을 다시 봅니다. 각 상자의 머리글에는 스레드 ID 및 이름을 표시하는 도구 설명이 있습니다. 병렬 스택 창에서 작업 보기로 전환합니다. 그림과 같이 머리글을 가리키면 작업 ID, 이름 및 상태가 표시됩니다.

Header tooltip in Parallel Stacks window

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

Screenshot of Grouped tasks in Tasks window.

Grouped tasks in Tasks window

다른 열을 기준으로 그룹화할 수도 있습니다. 작업을 그룹화하여 일부 작업에만 초점을 맞출 수 있습니다. 축소 가능한 그룹마다 함께 그룹화되는 항목 수가 있습니다.

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

바로 가기 메뉴에는 작업 상태에 따라 다양한 명령이 표시됩니다. 명령에는 복사, 모두 선택, 16진수 표시, 작업으로 전환, 할당된 스레드 중지, 이 스레드를 제외한 모든 스레드 중지, 할당된 스레드 재개플래그 설정이 포함될 수 있습니다.

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

요약

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