(C#) 닷넷 스레드 비동기 프로그래밍 (APM)

 

닷넷에서는 비동기 프로그래밍 처리를 지원하는 방식이 여러가지 있습니다. 이를 닷넷에서는 ‘비동기 프로그래밍 패턴’이라고 정하고 있습니다.
비동기 프로그래밍 패턴은 세 가지의 패턴이 있습니다.

  • IAsyncResult 형태의 콜백을 사용하는 APM 패턴(IAsyncResult 패턴) 링크
  • 이벤트 기반의 EAP 패턴 링크
  • 작업 기반의 TAP 패턴 이 방식은 .NET Framework 4에서 도입되었으며, 비동기 프로그래밍에 권장되는 방식 입니다. 링크

그럼 위 비동기 프로그래밍 패턴이 어떻게 사용되고 어떤 특징이 있는지 하나씩 살펴 보겠습니다.

IAsyncResult APM 패턴

APM 패턴은 System.IAsyncResult 인터페이스 형태를 사용하여 델리게이트로
비동기 작업의 실행 결과를 콜백(callback) 메서드 형태로 받는 구조 입니다.

기본적으로 APM 패턴을 따르는 비동기 메서드의 네이밍 규칙은 Begin○○○()와 End○○○()으로 되어 있습니다.
대표적으로 BCL 로는 System.Net.Sockets.Socket 클래스에
BeginAccept()
BeginConnect()
BeginReceive
BeginSend()
등 이 있습니다.

APM 패턴은 비동기 작업을 시작하고 System.IAsyncResult 인터페이스를 반환합니다.
반환된 정보는 비동기 작업에 대한 상태 등 이 있습니다.

순서을 좀 더 말해보면 우선 비동기로 처리할 작업 대상 메서드가 있고 이 메서드를 델리게이트 BeginInvoke()로 호출합니다. 이 것은 스레드풀에서 별도의 작업 스레드로 처리 됩니다.
BeginInvoke()호출시 비동기 작업의 결과를 받을 수 있는 System.AsyncCallback 델리게이트를 같이 넘겨 줍니다.
BeginInvoke()로 처리된 비동기 작업은 작업이 끝나면 넘겨받은 System.AsyncCallback 델리게이트로 다시 콜백 해줍니다.
콜백 시에는 현재 비동기 작업이 수행된 개체를 같이 넘겨서 해당 콜백 메서드 안에서 EndInvoke()로 결과를 받습니다.

public string GetDataSync(string param)
{
  Thread.Sleep(5000);
  return $"{param} server data";
}

위 처럼 약 5초가 걸리는 메서드가 있을때 System.IAsyncResult 를 사용한 비동기 처리는 다음과 같이 처리 될 수 있습니다.

Func<string, string> getDataFunc;

private IAsyncResult BeginGetData(string param, AsyncCallback asyncCallback, object state)
{
  getDataFunc = this.GetDataSync;
  return getDataFunc.BeginInvoke(param, asyncCallback, this);
}

private string EndGetData(IAsyncResult asyncResult)
{
  return getDataFunc.EndInvoke(asyncResult);
}

비동기로 시작할 수 있는 BeginGetData메서드가 있고 이 메서드에서 BeginInvoke()호출로 별도의 스레드 풀 스레드를 사용하여 비동기로 작업을 실행 합니다.</br> 이때 결과를 반환 받을 수 있는 System.AsyncCallback 델리게이트와 자신의 인스턴스를 같이 넘겨 줍니다.
그리고 EndInvoke()로 결과를 받는 메서드가 있습니다.
콜백 메서드는 다음과 같이 처리 할 수 있습니다.

private void GetDataCallBack(IAsyncResult asyncResult)
{
  if (asyncResult.AsyncState is APMExample example)
  {
    string result = example.EndGetData(asyncResult);
    Console.WriteLine(result);
  }
}

위 에서 자신의 인스턴스를 넘겨 주었기 때문에 EndGetData()메서드 호출로 EndInvoke()메서드를 사용할 수 있습니다.

실제 사용은 다음과 같은 코드가 될 수 있습니다.

IAsyncResult result = this.BeginGetData("AAA", GetDataCallBack, "파라메터");
Console.WriteLine("BeginGetData 호출");

결과는
BeginGetData 호출
[5초 이후]
AAA server data

작업이 끝날때 까지 블로킹

위 비동기 작업이 끝날때 기다려야 하는 경우 후행 처리는 블로킹이 되어야 합니다. 블로킹 처리를 하려면 System.IAsyncResult
AsyncWaitHandle()메서드 사용으로 처리 할 수 있습니다.

IAsyncResult result = this.BeginGetData("AAA", GetDataCallBack, "파라메터");
Console.WriteLine("BeginGetData 호출");
Console.WriteLine("대기");
result.AsyncWaitHandle.WaitOne();
Console.WriteLine("대기완료");

결과는
BeginGetData 호출
대기
[5초 이후]
AAA server data
대기완료

작업의 완료 상태에 대한 폴링 방법

비동기 작업이 끝날때까지 기다리는 동안 블로킹 하지 않고 비동기 작업이 끝났는지 계속해서 상태를 폴링 하여 처리 할 수 있습니다. 이때 사용할 수 있는 속성이
System.IAsyncResult 의 IsCompleted속성 입니다.

IAsyncResult result = this.BeginGetData("AAA", GetDataCallBack, "파라메터");
Console.WriteLine("BeginGetData 호출");
Console.WriteLine("대기");
while(result.IsCompleted == false)
{
  Console.WriteLine("비동기 작업 실행중..");
}
Console.WriteLine("대기완료");