본문 바로가기
카테고리 없음

[C#] 비동기 프로그래밍

by 오 복 이 2024. 11. 19.

비동기 프로그래밍은 시간 소모적인 작업이 프로그램의 흐름을 방해하지 않도록 하여, 더 빠르고 반응성이 좋은 애플리케이션을 만드는 데 필수적인 기법입니다. 


1. 비동기의 필요성과 개념

비동기 프로그래밍이란?

비동기 프로그래밍은 시간이 오래 걸리는 작업(예: 파일 입출력, 네트워크 요청, 데이터베이스 쿼리 등)을 별도로 처리하고, 나머지 작업은 계속 진행하도록 만드는 프로그래밍 방식입니다. 주요 목표는 애플리케이션의 반응성을 유지하고, 리소스 낭비를 줄이는 것입니다.

비동기의 필요성

  1. UI 애플리케이션의 반응성 유지
    예를 들어, 버튼 클릭 시 긴 작업이 진행되는 동안 UI가 멈추는 대신, 사용자는 여전히 애플리케이션과 상호작용할 수 있어야 합니다.
  2. 효율적인 리소스 사용
    CPU가 다른 작업을 처리할 수 있도록 대기 시간이 많은 작업(예: 네트워크 요청)은 비동기로 처리해야 합니다.
  3. I/O 작업 최적화
    파일 읽기/쓰기나 데이터베이스와 같은 작업은 비동기 방식으로 처리하는 것이 효율적입니다.

동기와 비동기의 비교

 

특징 동기 비동기
실행 방식 작업이 끝날 때까지 대기 작업을 시작한 뒤 다른 작업을 진행
UI 반응성 작업 중 UI가 멈춤 UI가 멈추지 않음
리소스 사용 작업 동안 차단 효율적으로 리소스 활용

2. async와 await 키워드

C#에서는 async와 await 키워드를 사용하여 비동기 작업을 간단히 구현할 수 있습니다.

async 키워드

  • 메서드가 비동기로 실행된다는 것을 나타냅니다.
  • 반환 타입은 보통 Task 또는 Task<T>입니다. 반환값이 없으면 Task를, 반환값이 있으면 Task<T>를 사용합니다.

await 키워드

  • 비동기 작업이 완료될 때까지 기다리며, 작업이 완료되면 실행 흐름을 이어갑니다.
  • await는 async 메서드 안에서만 사용할 수 있습니다.

비동기 메서드 예제

using System;
using System.Threading.Tasks;

class Program
{
    // 비동기 메서드 정의
    static async Task LongRunningTask()
    {
        Console.WriteLine("작업 시작");
        await Task.Delay(3000); // 3초 대기 (비동기적으로 대기)
        Console.WriteLine("작업 완료");
    }

    static async Task Main(string[] args)
    {
        Console.WriteLine("Main 시작");

        // 비동기 메서드 호출
        await LongRunningTask();

        Console.WriteLine("Main 종료");
    }
}

💡 Task.Delay(3000)은 3초 동안 비동기적으로 대기하는 작업입니다. 이 동안 다른 작업이 수행될 수 있습니다.


3. Task와 Task<T> 클래스의 활용

Task와 Task<T>는 비동기 작업을 나타내는 클래스입니다.

Task 클래스

Task는 반환값이 없는 비동기 작업을 나타냅니다.

static async Task PrintMessageAsync()
{
    await Task.Delay(2000); // 2초 대기
    Console.WriteLine("Hello, Async!");
}

static async Task Main()
{
    await PrintMessageAsync();
}​



Task<T> 클래스

Task<T>는 반환값이 있는 비동기 작업을 나타냅니다.

static async Task<int> GetNumberAsync()
{
    await Task.Delay(1000); // 1초 대기
    return 42; // 값 반환
}

static async Task Main()
{
    int result = await GetNumberAsync();
    Console.WriteLine($"결과: {result}");
}​



Task를 사용한 병렬 작업 처리

Task.WhenAll을 사용하면 여러 비동기 작업을 병렬로 실행하고, 모든 작업이 완료될 때까지 기다릴 수 있습니다.

static async Task<int> Task1()
{
    await Task.Delay(2000);
    Console.WriteLine("Task1 완료");
    return 1;
}

static async Task<int> Task2()
{
    await Task.Delay(1000);
    Console.WriteLine("Task2 완료");
    return 2;
}

static async Task Main()
{
    // 여러 Task 병렬 실행
    Task<int> t1 = Task1();
    Task<int> t2 = Task2();

    // 모든 작업 완료 대기
    int[] results = await Task.WhenAll(t1, t2);

    Console.WriteLine($"총합: {results[0] + results[1]}"); // 결과: 3
}​


💡 Task.WhenAll을 사용하면 병렬로 작업을 처리하여 성능을 최적화할 수 있습니다.


비동기 프로그래밍에서의 주의점

  1. 데드락 방지
    • UI 애플리케이션에서는 Task.Wait()나 Task.Result를 사용하면 데드락이 발생할 수 있습니다. 항상 await를 사용하세요.
      // 잘못된 예
      static void Main()
      {
          Task<int> task = GetNumberAsync();
          Console.WriteLine(task.Result); // 데드락 가능
      }




  2. Exception Handling
    • 비동기 메서드에서 발생한 예외는 try-catch를 통해 처리해야 합니다.

      static async Task<int> DivideAsync(int numerator, int denominator)
      {
          if (denominator == 0)
          {
              throw new DivideByZeroException("0으로 나눌 수 없습니다.");
          }
          return numerator / denominator;
      }
      
      static async Task Main()
      {
          try
          {
              int result = await DivideAsync(10, 0);
              Console.WriteLine($"결과: {result}");
          }
          catch (DivideByZeroException ex)
          {
              Console.WriteLine($"예외 발생: {ex.Message}");
          }
      }


     
  3. 캔슬레이션 토큰
    • 작업을 취소하려면 CancellationToken을 사용할 수 있습니다.
     

 

 

static async Task RunWithCancellation(CancellationToken token)
{
    for (int i = 0; i < 10; i++)
    {
        token.ThrowIfCancellationRequested();
        Console.WriteLine($"작업 {i} 진행 중...");
        await Task.Delay(500, token); // 취소 가능
    }
}

static async Task Main()
{
    CancellationTokenSource cts = new CancellationTokenSource();

    Task task = RunWithCancellation(cts.Token);

    await Task.Delay(1000); // 1초 대기 후 취소
    cts.Cancel();

    try
    {
        await task;
    }
    catch (OperationCanceledException)
    {
        Console.WriteLine("작업이 취소되었습니다.");
    }
}

 

728x90
반응형