본문 바로가기
C#

C# APM(Asynchronous Programming Model) 패턴

by 공부봇 2025. 1. 20.
반응형

C# APM(Asynchronous Programming Model) 패턴

APM(Asynchronous Programming Model)은 비동기 프로그래밍을 구현하는 오래된 패턴으로, .NET Framework 초기에 설계되었습니다. 이 패턴은 Begin/End 메서드 쌍을 사용하여 비동기 작업을 수행하고, 콜백 또는 IAsyncResult를 통해 결과를 처리합니다.

APM의 주요 개념

  1. Begin/End 메서드 쌍:
    • 비동기 작업을 시작하는 BeginXXX 메서드와 작업을 완료하고 결과를 가져오는 EndXXX 메서드로 구성됩니다.
    • 예: FileStream.BeginRead()와 FileStream.EndRead().
  2. IAsyncResult 인터페이스:
    • BeginXXX 메서드는 비동기 작업의 상태를 나타내는 IAsyncResult 객체를 반환합니다.
    • 주요 속성:
      • AsyncState: 사용자 정의 상태 객체.
      • AsyncWaitHandle: 작업이 완료될 때 신호를 설정하는 이벤트 핸들.
      • CompletedSynchronously: 작업이 동기적으로 완료되었는지 여부.
      • IsCompleted: 작업이 완료되었는지 여부.
  3. 콜백 메서드:
    • 비동기 작업이 완료되었을 때 호출되는 메서드입니다.
    • BeginXXX 메서드에 콜백을 전달하여 작업 완료 시 로직을 실행합니다.

APM의 기본 구조

using System;
using System.IO;

class Program
{
    static void Main()
    {
        string filePath = "example.txt";
        byte[] buffer = new byte[1024];
        FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, true);

        // BeginRead 시작
        fs.BeginRead(buffer, 0, buffer.Length, new AsyncCallback(ReadCallback), new AsyncState(fs, buffer));

        Console.WriteLine("Reading file asynchronously...");
    }

    // 콜백 메서드
    static void ReadCallback(IAsyncResult ar)
    {
        AsyncState state = (AsyncState)ar.AsyncState;
        FileStream fs = state.FileStream;
        byte[] buffer = state.Buffer;

        // 작업 완료 후 결과 가져오기
        int bytesRead = fs.EndRead(ar);
        Console.WriteLine($"Read {bytesRead} bytes.");

        // 리소스 정리
        fs.Close();
    }
}

// 사용자 정의 상태 클래스
class AsyncState
{
    public FileStream FileStream { get; }
    public byte[] Buffer { get; }

    public AsyncState(FileStream fileStream, byte[] buffer)
    {
        FileStream = fileStream;
        Buffer = buffer;
    }
}

APM의 특징

  • 장점:
    • 비동기 작업을 지원하며, 동시 작업 처리를 용이하게 만듭니다.
    • .NET 초기부터 제공되어 안정성이 검증된 패턴입니다.
  • 단점:
    • 코드가 복잡하고 가독성이 떨어질 수 있습니다.
    • 콜백 중첩으로 인해 관리가 어렵습니다.
    • 현대적인 비동기 프로그래밍 스타일에 비해 유지보수가 어렵습니다.

현대적인 대안

.NET 4.5 이후로, APM은 점차 **TAP(Task-based Asynchronous Pattern)**으로 대체되었습니다. TAP은 async/await 키워드를 사용하여 비동기 작업을 보다 쉽게 관리할 수 있도록 설계되었습니다.

예를 들어, 위의 APM 코드는 TAP에서 다음과 같이 간단히 표현할 수 있습니다:

using System;
using System.IO;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        string filePath = "example.txt";
        byte[] buffer = new byte[1024];

        using FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
        int bytesRead = await fs.ReadAsync(buffer, 0, buffer.Length);
        Console.WriteLine($"Read {bytesRead} bytes.");
    }
}

APM은 여전히 일부 오래된 API에서 사용되지만, 새로운 코드에서는 TAP 사용을 권장합니다.

 

APM을 TAP으로 변환하는 방법과 APM의 실제 예제


1. APM을 TAP(Task-based Asynchronous Pattern)으로 변환하기

.NET에서는 TaskFactory.FromAsync 메서드를 통해 APM 기반 메서드를 TAP 기반으로 변환할 수 있습니다.

다음은 APM 메서드(BeginXXX, EndXXX)를 TAP 스타일로 변환하는 예제입니다.

코드 예제 (APM -> TAP 변환)

using System;
using System.IO;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        string filePath = "example.txt";
        byte[] buffer = new byte[1024];

        using FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, true);

        // APM을 TAP으로 변환
        int bytesRead = await Task.Factory.FromAsync(
            fs.BeginRead, // Begin 메서드
            fs.EndRead,   // End 메서드
            buffer,       // 버퍼
            0,            // 오프셋
            buffer.Length, // 길이
            null          // 상태 객체
        );

        Console.WriteLine($"Read {bytesRead} bytes.");
    }
}

코드 설명

  • Task.Factory.FromAsync는 BeginXXX와 EndXXX 메서드를 받아 TAP 스타일의 Task 객체를 생성합니다.
  • await 키워드를 사용하여 비동기 작업을 간단하게 처리할 수 있습니다.

2. APM의 실제 예제

APM 예제 코드

다음은 파일을 비동기로 읽는 APM 코드입니다.

using System;
using System.IO;

class Program
{
    static void Main()
    {
        string filePath = "example.txt";
        byte[] buffer = new byte[1024];
        FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, true);

        // BeginRead로 비동기 작업 시작
        fs.BeginRead(buffer, 0, buffer.Length, new AsyncCallback(ReadCallback), new AsyncState(fs, buffer));

        Console.WriteLine("Reading file asynchronously...");
        Console.ReadLine(); // 콜백 완료를 기다리기 위해 콘솔 대기
    }

    // 콜백 메서드
    static void ReadCallback(IAsyncResult ar)
    {
        AsyncState state = (AsyncState)ar.AsyncState;
        FileStream fs = state.FileStream;
        byte[] buffer = state.Buffer;

        // 작업 완료 후 결과 가져오기
        int bytesRead = fs.EndRead(ar);
        Console.WriteLine($"Read {bytesRead} bytes.");

        // 리소스 정리
        fs.Close();
    }
}

// 사용자 정의 상태 클래스
class AsyncState
{
    public FileStream FileStream { get; }
    public byte[] Buffer { get; }

    public AsyncState(FileStream fileStream, byte[] buffer)
    {
        FileStream = fileStream;
        Buffer = buffer;
    }
}

실행 흐름

  1. FileStream.BeginRead 메서드로 비동기 읽기를 시작합니다.
  2. 작업이 완료되면 ReadCallback이 호출됩니다.
  3. FileStream.EndRead를 호출하여 읽은 데이터를 가져옵니다.
  4. 파일 스트림을 닫아 리소스를 정리합니다.

APM과 TAP 비교

특징 APM TAP

코드 복잡도 복잡 (콜백 메서드 필요) 간단 (async/await 사용)
가독성 낮음 높음
지원 범위 .NET 초기 API .NET 4.5+ API
유지보수 용이성 어렵다 쉽다

3. 두 패턴 비교 예제

APM 코드

fs.BeginRead(buffer, 0, buffer.Length, new AsyncCallback(ReadCallback), state);
int bytesRead = fs.EndRead(ar);

TAP 코드

int bytesRead = await fs.ReadAsync(buffer, 0, buffer.Length);

 

반응형