본문 바로가기
C#/쓰레드

C# 태스크(Task)와 비동기 프로그래밍: 생성, 연속 작업, 부모-자식 관계 이해

by 공부봇 2025. 1. 20.
반응형
    internal class Program
    {
        static int TaskMethod(string name, int second)
        {
            DateTime now = DateTime.Now;
            string formattedTime = now.ToString("HH:mm:s:fff");

            Console.WriteLine($"[{formattedTime}] " + "Task {0} is running on a thread id {1}. Is thread pool thread: {2}", 
                name,
                Thread.CurrentThread.ManagedThreadId,
                Thread.CurrentThread.IsThreadPoolThread);

            Thread.Sleep(TimeSpan.FromSeconds(second));

            return 42 * second;
        }


        static void Main(string[] args)
        {
            var firstTask = new Task<int>(() => TaskMethod("First Task", 3));
            var secondTask = new Task<int>(() => TaskMethod("Second Task", 2));

            DateTime now = DateTime.Now;
            string formattedTime = now.ToString("HH:mm:s:fff");

            firstTask.ContinueWith(
                t => Console.WriteLine($"[{formattedTime}] " + "The first answer is {0}, Thread id {1}, is thread pool thread: {2}",
                t.Result,
                Thread.CurrentThread.ManagedThreadId,
                Thread.CurrentThread.IsThreadPoolThread),
                TaskContinuationOptions.OnlyOnRanToCompletion
                );

            firstTask.Start();
            secondTask.Start();

            Thread.Sleep(TimeSpan.FromSeconds(4));

            now = DateTime.Now;
            formattedTime = now.ToString("HH:mm:s:fff");

            Task continuation = secondTask.ContinueWith(
                t => Console.WriteLine($"[{formattedTime}] " + "The second answer is {0}, Thread id {1}, is thread pool thread: {2}",
                t.Result,
                Thread.CurrentThread.ManagedThreadId,
                Thread.CurrentThread.IsThreadPoolThread),
                TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.ExecuteSynchronously);

            now = DateTime.Now;
            formattedTime = now.ToString("HH:mm:s:fff");

            continuation.GetAwaiter().OnCompleted(
                () => Console.WriteLine($"[{formattedTime}] " + "Continuation Task Completed! Thread id {0}, is thread pool thread: {1}",
                Thread.CurrentThread.ManagedThreadId,
                Thread.CurrentThread.IsThreadPoolThread)
                );

            Thread.Sleep(TimeSpan.FromSeconds(2));
            Console.WriteLine();

            firstTask = new Task<int>(() =>
            {
                var innerTask = Task.Factory.StartNew(() => TaskMethod("Second Task", 5), TaskCreationOptions.AttachedToParent);

                innerTask.ContinueWith(t => TaskMethod("Third Task", 2), TaskContinuationOptions.AttachedToParent);

                return TaskMethod("First Task", 2);
            });

            firstTask.Start();

            while (!firstTask.IsCompleted)
            {
                now = DateTime.Now;
                formattedTime = now.ToString("HH:mm:s:fff");

                Console.WriteLine($"[{formattedTime}] " + firstTask.Status);
                Thread.Sleep(TimeSpan.FromSeconds(0.5));
            }

            now = DateTime.Now;
            formattedTime = now.ToString("HH:mm:s:fff");

            Console.WriteLine($"[{formattedTime}] " + firstTask.Status);

            Thread.Sleep(TimeSpan.FromSeconds(10));
        }
    }

 

 

 

태스크(Task)에 대한 정리


개요

  • 이 코드는 C#의 Task(태스크)와 비동기 처리 기법을 활용하여 독립적인 태스크 생성, 연속(continuation) 작업, 부모-자식 관계 태스크 등을 학습하기 위한 예제입니다.
  • 주요 학습 포인트:
    1. 독립적인 태스크 생성 및 실행.
    2. 선행 작업 완료 후 실행되는 연속 작업.
    3. 태스크 연속 작업의 동기적 실행.
    4. 비동기 프로그래밍과 OnCompleted 사용.
    5. 부모-자식 관계 태스크의 동작.

코드 분석

1. 독립적인 태스크 생성 및 실행

  • 두 개의 독립적인 태스크를 생성합니다.
    var firstTask = new Task<int>(() => TaskMethod("First Task", 3));
    var secondTask = new Task<int>(() => TaskMethod("Second Task", 2));
    
    • 각 태스크는 별도의 스레드에서 실행되며, 서로 간섭하지 않습니다.
    • TaskMethod는 태스크의 이름, 실행 시간, 스레드 정보를 출력하며, 결과(42 * 실행 시간)를 반환합니다.

2. 태스크 연속 작업 설정

  • ContinueWith를 사용하여 태스크가 완료된 후 실행할 연속 작업을 정의합니다.
    firstTask.ContinueWith(
        t => Console.WriteLine("The first answer is {0}, Thread id {1}, is thread pool thread: {2}",
        t.Result,
        Thread.CurrentThread.ManagedThreadId,
        Thread.CurrentThread.IsThreadPoolThread),
        TaskContinuationOptions.OnlyOnRanToCompletion
    );
    
    • OnlyOnRanToCompletion 옵션을 사용하여 선행 작업이 성공적으로 완료된 경우에만 실행합니다.

3. 태스크 시작 및 대기

  • 두 태스크를 시작하고, Thread.Sleep으로 4초 동안 대기하여 태스크들이 종료될 시간을 줍니다.
    firstTask.Start();
    secondTask.Start();
    Thread.Sleep(TimeSpan.FromSeconds(4));
    

4. 동기적 연속 작업

  • secondTask에 대해 연속 작업을 추가하며, TaskContinuationOptions.ExecuteSynchronously 옵션을 지정합니다.
    Task continuation = secondTask.ContinueWith(
        t => Console.WriteLine("The second answer is {0}, Thread id {1}, is thread pool thread: {2}",
        t.Result,
        Thread.CurrentThread.ManagedThreadId,
        Thread.CurrentThread.IsThreadPoolThread),
        TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.ExecuteSynchronously
    );
    
    • ExecuteSynchronously: 연속 작업이 새로운 스레드 대신 현재 실행 중인 스레드에서 동기적으로 실행됩니다.
    • 짧은 수명의 작업일 경우 성능 최적화를 위해 유용합니다.

5. OnCompleted 사용

  • GetAwaiter().OnCompleted를 통해 태스크 완료 후 실행할 코드를 정의합니다.
    continuation.GetAwaiter().OnCompleted(
        () => Console.WriteLine("Continuation Task Completed! Thread id {0}, is thread pool thread: {1}",
        Thread.CurrentThread.ManagedThreadId,
        Thread.CurrentThread.IsThreadPoolThread)
    );
    
    • 비동기 작업 완료 후 추가적인 처리를 수행합니다.

6. 부모-자식 관계 태스크

  • 새로운 부모 태스크를 생성하며, 내부에서 자식 태스크를 실행합니다.
    firstTask = new Task<int>(() =>
    {
        var innerTask = Task.Factory.StartNew(() => TaskMethod("Second Task", 5), TaskCreationOptions.AttachedToParent);
    
        innerTask.ContinueWith(t => TaskMethod("Third Task", 2), TaskContinuationOptions.AttachedToParent);
    
        return TaskMethod("First Task", 2);
    });
    
    • TaskCreationOptions.AttachedToParent:
      • 자식 태스크가 부모 태스크와 연결됩니다.
      • 부모 태스크는 자식 태스크들이 모두 완료될 때까지 종료되지 않습니다.
    • 자식 태스크는 Second Task와 Third Task를 실행합니다.

실행 결과 예상

1. 각 태스크 실행

  • TaskMethod가 실행되며 태스크의 이름, 실행 시간, 스레드 정보를 출력합니다.
    [14:12:34:123] Task First Task is running on a thread id 5. Is thread pool thread: True
    [14:12:34:456] Task Second Task is running on a thread id 6. Is thread pool thread: True
    

2. 연속 작업 실행

  • firstTask와 secondTask 완료 후 각각의 연속 작업이 실행됩니다.
    [14:12:37:123] The first answer is 126, Thread id 7, is thread pool thread: True
    [14:12:36:456] The second answer is 84, Thread id 6, is thread pool thread: True
    

3. OnCompleted 실행

  • OnCompleted 메서드가 호출되어 연속 작업 완료 후 메시지를 출력합니다.
    [14:12:36:789] Continuation Task Completed! Thread id 6, is thread pool thread: True
    

4. 부모-자식 태스크 실행

  • 부모 태스크가 완료되기 전에 자식 태스크(Second Task, Third Task)가 실행됩니다.
    [14:12:39:123] Task Second Task is running on a thread id 8. Is thread pool thread: True
    [14:12:44:456] Task Third Task is running on a thread id 9. Is thread pool thread: True
    [14:12:46:789] Task First Task is running on a thread id 8. Is thread pool thread: True
    

5. 태스크 상태 출력

  • 부모 태스크의 상태를 지속적으로 확인하며 출력합니다.
    [14:12:40:000] Running
    [14:12:41:000] Running
    ...
    [14:12:46:789] RanToCompletion
    

학습 포인트

  1. 독립적인 태스크 생성 및 실행:
    • Task를 사용하여 비동기 작업을 독립적으로 실행할 수 있다.
  2. 연속 작업:
    • ContinueWith를 통해 선행 작업 완료 후 실행할 작업을 정의할 수 있다.
  3. 동기적 연속 작업:
    • 짧은 작업을 동기적으로 처리하여 스레드 풀 부담을 줄이고 성능을 최적화한다.
  4. 비동기 기법 통합:
    • OnCompleted를 활용하여 비동기 프로그래밍과 태스크 연속 작업을 결합한다.
  5. 부모-자식 태스크 관계:
    • 자식 태스크가 완료될 때까지 부모 태스크가 종료되지 않는 구조를 이해한다.

결론

이 코드는 C#에서 Task와 비동기 처리 기법을 학습하기에 적합한 예제입니다. 태스크 생성, 연속 작업 설정, 동기적 실행, 부모-자식 태스크 관리 등의 개념을 이해하는 데 큰 도움이 됩니다. 추가 질문이나 특정 부분에 대한 설명이 필요하면 알려주세요!

반응형