C#

Task

Barbarian developer 2024. 11. 3.

Task는 C#에서 비동기 프로그래밍을 수행하기 위한 주요 클래스입니다.

비동기 프로그래밍을 통해 프로그램이 작업을 기다리는 동안 다른 작업을 수행할 수 있어 응답성이 높은 애플리케이션을 만들 수 있습니다.

 

Task의 기본 개념

  • System.Threading.Tasks 네임스페이스에 포함되어 있습니다.
  • Task는 비동기 작업을 나타내며, 비동기 코드의 실행 결과를 반환할 수 있습니다.
  • Task를 통해 비동기 작업이 완료될 때까지 기다리거나, 완료 후에 후속 작업을 연결할 수 있습니다.

비동기 작업이란?

  • 프로그램이 작업을 시작하면 그 작업이 백그라운드에서 실행되고, 프로그램은 다음 코드 줄로 넘어갑니다.
  • 작업이 백그라운드에서 진행되는 동안 프로그램은 다른 작업을 수행하거나 다음 코드 줄로 넘어갈 수 있습니다.

Task의 기본 사용법

  1. Task 생성 및 실행
var myTask = Task.Run( ()=> //Task의 생성과 시작을 단번에 합니다. Task가 실행할 Action 대리자도 무명함수로 바꿉니다.
	{
     	Thread.cleep(1000); //1초 대기
        Console.WriteLine("Printed asynchronously.");
    }
};


Console.WriteLine("Printed synchronously.");

myTask.Wati();

/*결과는
Printed synchronously.
Printed asynchronously. */
using System;
using System.Threading.Tasks;

class Program
{
    static void Main(string[] args)
    {
        // 컴퓨터가 빨래를 비동기적으로 시작함
        Task doLaundry = Task.Run(() => {
            Console.WriteLine("빨래를 시작합니다.");
            Task.Delay(2000).Wait(); // 2초 동안 기다림 (빨래하는 중)
            Console.WriteLine("빨래가 끝났습니다.");
        });

        // 컴퓨터가 설거지를 비동기적으로 시작함
        Task doDishes = Task.Run(() => {
            Console.WriteLine("설거지를 시작합니다.");
            Task.Delay(1000).Wait(); // 1초 동안 기다림 (설거지하는 중)
            Console.WriteLine("설거지가 끝났습니다.");
        });

        // 메인 프로그램은 다른 일을 할 수 있음
        Console.WriteLine("나는 숙제를 합니다.");

        // 모든 Task가 끝날 때까지 기다림
        doLaundry.Wait();
        doDishes.Wait();

        Console.WriteLine("모든 집안일이 끝났습니다!");
    }
}

 

<실행결과>

 

코드의 비동기 실행결과를 주는 Task<TResult>클래스

Task<TResult>란?

  • Task<TResult>는 비동기 작업을 수행한 후 결과값을 반환하는 클래스입니다. 즉, 컴퓨터가 어떤 작업을 하고 나서 그 작업의 결과를 가져오는 비동기 작업이라고 생각하면 됩니다.
  • TResult는 작업이 완료된 후 반환할 데이터의 타입을 의미합니다. 예를 들어, Task<int>는 작업이 끝나면 int 타입의 값을 반환한다는 뜻입니다.

 

Task<TResult> 사용법

using System;
using System.Threading.Tasks;

class Program
{
    static void Main(string[] args)
    {
        // 비동기 작업을 실행하고 그 결과값을 받는 Task<int> 사용
        Task<int> resultTask = Task.Run(() => {
            Console.WriteLine("숫자를 계산합니다...");
            Task.Delay(2000).Wait(); // 2초 대기 (계산하는 중)
            return 42; // 계산 결과를 반환
        });

        Console.WriteLine("다른 작업을 수행 중...");

        // 결과를 기다렸다가 사용
        int result = resultTask.Result;
        Console.WriteLine($"계산된 결과는: {result}");
    }
}

 

 

  • Task<int> resultTask: 이 Task는 비동기 작업을 실행하고 int 타입의 결과값을 반환합니다.
  • Task.Run(() => {...}): 비동기 작업을 시작하고 42라는 값을 반환하는 람다 함수가 실행됩니다.
  • resultTask.Result: Task가 완료될 때까지 기다렸다가 42라는 값을 가져와서 출력해. 이 부분에서 프로그램은 잠깐 멈춰서 Task의 결과를 기다리지만, 그 외에는 다른 작업을 계속할 수 있습니다.

 

손쉬운 병렬 처리를 가능케 하는 Parallel 클래스

  • Parallel 클래스는 여러 작업을 동시에 실행하기 위해 사용되는 C#의 기능이야. 이 클래스는 다중 코어 CPU를 효율적으로 활용해서 병렬 처리를 쉽게 구현할 수 있게 해줍니다.
  • 예를 들어, 반복문을 쓸 때 한 번에 하나씩 실행하는 대신, 여러 작업을 병렬로 나눠서 동시에 실행하게 만듭니다.

언제 사용할까?

  • 데이터나 연산이 많은 작업을 빠르게 처리하고 싶을 때.
  • 병렬로 나눠서 실행할 수 있는 반복 작업이나 여러 개의 독립적인 작업을 할 때.

가장 많이 쓰이는 메서드:

  1. Parallel.For: for 루프처럼 작동하지만, 각 반복을 병렬로 실행합니다.
  2. Parallel.ForEach: 컬렉션의 각 항목을 병렬로 처리할 때 사용합니다.

Parallel.For

using System;
using System.Threading.Tasks;

class Program
{
    static void Main(string[] args)
    {
        // 1부터 10까지의 숫자를 병렬로 출력
        Parallel.For(1, 11, i =>
        {
            Console.WriteLine($"숫자 {i} 처리 중... (스레드 ID: {Task.CurrentId})");
        });

        Console.WriteLine("모든 작업이 완료되었습니다!");
    }
}

 

<실행결과>

 

  • Parallel.For(1, 11, i => {...})는 1부터 10까지의 숫자를 병렬로 출력합니다. 즉, 각 숫자에 대해 코드 블록이 동시에 실행돼서 작업 속도가 더 빨라질 수 있습니다.
  • Task.CurrentId는 현재 작업의 ID를 출력해서 병렬로 실행되는 각 작업이 다른 스레드에서 실행되고 있다는 걸 보여주줍니다.

Parallel.ForEach

using System;
using System.Collections.Generic;
using System.Threading.Tasks;

class Program
{
    static void Main(string[] args)
    {
        // 씻어야 할 과일 목록
        List<string> fruits = new List<string> { "사과", "바나나", "체리", "딸기", "포도" };

        // 각 과일을 병렬로 처리
        Parallel.ForEach(fruits, fruit =>
        {
            // 각 과일을 씻는 중
            Console.WriteLine($"{fruit} 씻는 중... (스레드 ID: {Task.CurrentId})");
        });

        Console.WriteLine("모든 과일을 다 씻었습니다!");
    }
}

 

 

<실행결과>

 

 

  • Parallel.ForEach는 items 리스트의 각 항목을 동시에 처리합니다. 각 과일 이름을 병렬로 출력하니까 작업 속도가 더 빠를 수 있죠.
  • 마찬가지로, Task.CurrentId를 통해 병렬 작업이 서로 다른 스레드에서 수행되는 걸 확인할 수 있었습니다.

 

async 한정자와 await연산자로 만드는 비동기 코드

 

async와 await란?

  • async 한정자는 메서드나 함수가 비동기 작업을 할 수 있게 만들어주는 표시입니다. 이걸 붙이면 그 메서드 안에서 await를 사용할 수 있습니다.
  • await 연산자는 비동기 작업이 완료될 때까지 잠깐 기다렸다가, 완료되면 이어서 다음 작업을 계속하도록 해줍니다. 하지만 기다리는 동안 메인 스레드는 멈추지 않고 다른 일을 할 수 있죠.

왜 사용하는 걸까요?

  • 프로그램이 오래 걸리는 작업(예: 파일 읽기, 웹 요청, 데이터베이스 쿼리 등)을 할 때, 그 작업이 끝날 때까지 기다리면 프로그램이 멈춰서 다른 작업을 못 합니다. 하지만 async와 await를 사용하면 그 작업이 끝나기를 기다리는 동안에도 다른 일을 계속할 수 있죠.
using System;
using System.IO;
using System.Threading.Tasks;

class Program
{
    static async Task Main(string[] args)
    {
        Console.WriteLine("파일 읽기를 시작합니다...");

        // 비동기 파일 읽기 호출
        string content = await ReadFileAsync("test.txt");

        Console.WriteLine("파일 읽기 완료!");
        Console.WriteLine($"파일 내용: {content.Substring(0, Math.Min(100, content.Length))}..."); // 첫 100자만 출력
    }

    static async Task<string> ReadFileAsync(string filePath)
    {
        using (StreamReader reader = new StreamReader(filePath))
        {
            // 비동기적으로 파일 내용을 읽음
            string content = await reader.ReadToEndAsync();
            return content; // 작업이 완료되면 내용을 반환
        }
    }
}

 

<실행 결과>

 

  • ReadFileAsync 메서드: StreamReader를 사용해서 파일을 읽고, await reader.ReadToEndAsync()로 비동기적으로 파일 내용을 읽습니다.
  • await의 역할: 파일 읽기가 끝날 때까지 기다리지만, 그 동안 프로그램은 다른 일을 할 수 있습니다.
  • 결과: 파일 내용이 모두 읽히면 그제서야 content 변수에 값이 할당되고 출력됩니다.

.Net이 제공하는 비동기 API 맛보기

  • 여러가지의 API가 있지만, 두가지만 다룬다고 합니다.
  • ReadAsync와 WriteAsync는 비동기 입출력(I/O) 작업을 수행하는 API입니다.
  • 이 메서드들은 파일이나 데이터 스트림에서 비동기적으로 읽고 쓰는 작업을 할 때 사용됩니다.

ReadAsync 메서드

  • 역할: 파일이나 스트림에서 비동기적으로 데이터를 읽어오는 메서드입니다.
  • 사용 예시: 큰 파일이나 네트워크 스트림에서 데이터를 읽을 때, 프로그램이 멈추지 않고 다른 작업을 할 수 있도록 해줍니다.
using System;
using System.IO;
using System.Threading.Tasks;

class Program
{
    static async Task Main(string[] args)
    {
        using (FileStream fs = new FileStream("example.txt", FileMode.Open, FileAccess.Read))
        {
            byte[] buffer = new byte[1024]; // 읽어올 데이터를 담을 버퍼
            int bytesRead = await fs.ReadAsync(buffer, 0, buffer.Length); // 비동기적으로 데이터 읽기
            Console.WriteLine($"읽은 바이트 수: {bytesRead}");
            Console.WriteLine($"내용: {System.Text.Encoding.UTF8.GetString(buffer, 0, bytesRead)}");
        }
    }
}

 

 

WriteAsync 메서드

  • 역할: 파일이나 스트림에 비동기적으로 데이터를 쓰는 메서드입니다.
  • 사용 예시: 파일이나 네트워크 스트림에 데이터를 쓸 때, 프로그램이 멈추지 않고 다른 작업을 할 수 있도록 해줍니다.
using System;
using System.IO;
using System.Text;
using System.Threading.Tasks;

class Program
{
    static async Task Main(string[] args)
    {
        using (FileStream fs = new FileStream("output.txt", FileMode.Create, FileAccess.Write))
        {
            byte[] buffer = Encoding.UTF8.GetBytes("Hello, 비동기 쓰기!");
            await fs.WriteAsync(buffer, 0, buffer.Length); // 비동기적으로 데이터 쓰기
            Console.WriteLine("파일 쓰기 완료");
        }
    }
}

 

ReadAsync와 WriteAsync의 장점:

  • 효율성: 비동기 작업 중에도 프로그램이 멈추지 않고 다른 일을 계속할 수 있어.
  • 응답성: 특히 UI 프로그램에서 사용자 경험을 향상시켜. 예를 들어, 큰 파일을 읽는 동안 UI가 멈추지 않아.
  • 리소스 최적화: 여러 비동기 작업을 할 때 스레드 풀을 효율적으로 사용해서 시스템 자원을 절약할 수 있어.

'C#' 카테고리의 다른 글

c#테트리스 게임만들기  (0) 2024.11.04
네트워크 프로그래밍  (0) 2024.11.04
대리자와 이벤트  (0) 2024.11.01
프로그래밍  (0) 2024.11.01
배열과 컬렉션 그리고 인덱서  (0) 2024.11.01

댓글