Welcome to Parallel C#(4) - 작업의 기본 Part 2.

C# Parallel Programming 2010. 6. 3. 09:00 Posted by 알 수 없는 사용자

- 잡설없이 바로 고고고!

지난 포스트의 마지막 예제에서 Task 클래스의 IsCompleted라는 속성을 사용했었습니다. 이름 그대로, 해당 작업이 끝났는지를 확인할 수 있는 속성인거죠. 이런 속성말고는 또 뭐가 있을까요? 몇 가지 쓸모 있는 속성을 들을 예제를 통해서 확인해보도록 하죠.

using System;
using System.Threading.Tasks;

namespace Exam4
{
    class Program
    {
        static string Calc(object from)
        {
            long sum = 0;
            long start = (long)from;
            Console.WriteLine("현재 이 메서드를 실행중인 스레드 ID : {0}",
                Task.CurrentId);

            for (long i = start; i < 100000000; i++)
            {
                sum += i;
            }
            return sum.ToString();
        }

        static void Main(string[] args)
        {
            Task<string> task = new Task<string>(Calc, 1L);

            task.Start();

            Console.WriteLine("추가 스레드의 ID {0}",
                task.Id);

            Console.WriteLine("추가 스레드에 제공된 상태 값 : {0}",
                task.AsyncState.ToString());

            while (!task.IsCompleted)
            {
                if (task.Status == TaskStatus.Running)
                {
                    Console.Write(".");
                }
            }

            Console.WriteLine("");
            //여기서 추가 스레드가 끝날 때 까지 기다린다.
            Console.WriteLine(task.Result);
            System.Diagnostics.Trace.Assert(
                task.IsCompleted);
        }
    }
}

<코드1> 유용한 속성을 사용한 예제

<코드1>은 기본적으로 유용하게 사용되는 속성들을 활용한 간단한 예제입니다. 우선 결과를 볼까요?

추가 스레드의 ID 1
현재 이 메서드를 실행중인 스레드 ID : 1
추가 스레드에 제공된 상태 값 : 1
................................................................................................................................................................
................................................................................................................................................................
................................................................................................................................................................
................................................................................................................................................................
................................................................................................................................................................
................................................................................................................................................................
...............................................................................................................................................................
................................................................................................................................................................
-중략-
................................................................................................................................................................
......................................................................................
4999999950000000
계속하려면 아무 키나 누르십시오 . . .
<결과1> 실행 결과

그럼 속성을 하나씩 확인 해보면서 왜 저런 결과가 나왔는지 확인해보죠.

 속성명  설명 
 Task.CurrentId  현재 Task.CurrentId 호출을 처리하는 스레드의 Id
 AsyncState  스레드에 추척가능한 값을 부여한다. 예를 들어서 스레드가 3개가 있고, 각각 1,2,3 이라는 값을 부여했다면, 각각의 스레드의 AsyncState는 1,2,3 이므로 이 값을 다른 용도로 사용할 수도 있고, 스레드를 구별하는데 사용할 수도 있다. 
 Id  각각의 작업에 부여되는 고유한 Id
 Status  현재 작업의 상태를 표시한다. Created, WatingForActivation, WaitingForRun, Running, WaitingForChildrenToComplete, RanToComplete, Canceled, Faulted등이 있다. 
<표1> 유용한 속성들

네 각각의 속성을 한번 정리해봤습니다. 어헣. 추가 스레드의 아이디는 1이었죠. 그리고, Calc메서드를 실행하는 스레드도 역시 아이디가 1인 추가 스레드이기 때문에, Calc메서드 안에서 Task.CurrentId로 현재 실행중인 스레드의 아이디를 출력하면 1이 출력되는 것이구요. 스레드를 생성하면서, 상태값으로 1을 넘겨줍니다. 그래서, 그 상태값을 Calc메서드 내부에서 받아서 사용하기도 하고, 추후에 스레드의 상태값을 출력해볼 수도 있는 것이죠.


- 스레드 자세히 들여다 보기

그럼 비주얼 스튜디오에 새로 추가된 툴을 이용해서, 어떤 스레드가 생성되는지 한번 확인해보도록 하겠습니다. 아마, 뒤에서 더 자세하게 설명드리겠지만, 오늘은 그냥 간단하게 살펴보는 정도로 하도록 하죠. <코드1>에 아래와 같이 코드를 추가합니다.

Debugger.Break();

Console.WriteLine("추가 스레드의 ID {0}",
    task.Id);

Console.WriteLine("추가 스레드에 제공된 상태 값 : {0}",
    task.AsyncState.ToString());
           
while (!task.IsCompleted)
{
    if (task.Status == TaskStatus.Running)
    {
        Debugger.Break();
        Console.Write(".");
    }
}

<코드2> 수정한 코드

Debugger클래스는 System.Diagnostics에 정의되어 있는데요, 브레이크 포인트를 걸지 않아도, Break메서드가 호출된 곳에서 실행을 멈추고 디버거를 사용할 수 있습니다. 다만, 그냥 Debug모드로 놓고 컴파일하지 않고 실행(Ctrl + F5)을 하면, 계속 에러가 나니 주의하시구요.

우선 F5로 실행을 하신다음에, 첫번째 브레이크 포인트에 걸리면, '디버그'메뉴에서 '창'메뉴로 들어가서 '스레드', '병렬스택', '병렬작업'을 띄웁니다. 그리고 '스레드'를 보면,

<그림1> 스레드 창

다음과 같이 현재 떠있는 스레드의 목록을 볼 수 있습니다. 저 중에, '.NET SystemEvents'라고 된 스레드는 이벤트의 발생을 주시하고 있는 스레드이구요, 'vshost.RunParkingWindow'라고 된 스레드는 비주얼 스튜디오 호스팅 프로세스입니다. 그리고 '주 스레드'는 현재 Main메서드를 실행 중인 스레드를 말하구요. 그리고 ID가 4112라고 된 스레드가 추가로 생성한 스레드입니다. 어째서 그런지 확인을 해볼까요? 병렬 스택 창을 한번 확인해보죠.


<그림2> 병렬 스택 창

위 그림은 병렬 스택 창에서 스레드 그룹의 제목('4개 스레드'라고 쓰인 부분)에 마우스를 올리면 어떤 스레드가 있는지 보여주는 장면입니다. 총 3개의 스레드가 있는데, 앞에서 설명드린 3개의 스레드외에 작업자 스레드가 하나 있는 걸 보실 수 있습니다. 바로 저 스레드가 우리가 추가로 생성한 스레드인거죠. 그런데, 호출 스택에 Main메서드를 실행하는 주 스레드만 한 단계 진행한 걸 볼 수 있는데요, 그렇다면 아직 추가 스레드에 작업이 물린 상태는 아닌 것 같습니다. 그러면, F5키를 눌러서 다음으로 넘어가 볼까요? 그리고 병렬 스택을 보면,


<그림 3> 바뀐 병렬 스택 창

이미 추가 스레드도 작업에 들어간 상태이다 보니, 추가 스레드가 한 단계 더 진행해서, Calc메서드를 실행하고 있다고 나옵니다. 그리고 병렬 작업 창을 한번 볼까요?

<그림 4> 병렬 작업 창

상태는 실행 중(== TaskStatus.Running)이고, 할당된 스레드는 4112번 작업자 스레드라는 걸 확인할 수 있습니다. 이런 디버깅 툴을 잘 활용하면, 복잡한 병렬 프로그래밍을 하는 데 좀 더 편안하게 작업할 수 있겠죠? 이 디버깅 툴에 대해서는 나중에 좀 더 자세하게 설명드리도록 하지용.


- 마무리

앤더스 헬스버그는 C#에 영향을 미치는 3대 트렌드가 선언적, 동시적, 동적 프로그래밍 이라고 했는데요, 선언적 프로그래밍은 이미 LINQ를 통해서 편하게 지원되고, PLINQ로 확장이 되었죠. 그리고 동적 프로그래밍은 dynamic타입과 DLR을 통해서 지원이 되구요. 그리고 동시적 프로그래밍은 TPL과 이런 다양한 툴을 통해서 좀 더 제대로된 지원을 하고 있습니다. 제대로만 쓴다면, 비주얼 스튜디오 2010에서 쫌 편하게 작업할 수 있겠네요. 늘 문제가 되는건 제대로 쓸 줄도 모르면서 불평만 하는 저 같은 양민이져 어헣-_-.


- 참고자료

1. Essential C# 4.0, Mark Michaelis, Addison Wesley
2. http://msdn.microsoft.com/en-us/library/dd554943.aspx
3. http://msdn.microsoft.com/en-us/library/microsoft.win32.systemevents.aspx
4. http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/f8ccec3a-25db-4d3b-a90a-e758f6243356/