2023. 3. 21. 11:39ㆍC#
이 글을 읽기전에 필요한 기초 지식은 아래와 같으니 검색을 통해 먼저 숙달하고 본 포스팅을 읽도록 하자
블로킹 vs 논블로킹 함수
동기 vs 비동기 프로그래밍
이 글을 작성하는 시점에서 C#의 비동기 프로그래밍 기술은 크게 APM (Asynchronous Programming Model)과 EAP(Event-based Asynchronous Pattern) 그리고 마지막으로 TAP (Task-based Asynchronous Programming) 로 나눌 수 있다.
이 중 현재 시점에서 C#의 가장 진보된 비동기 프로그래밍 스킬하면 TAP 인데 이번 포스팅에서는 APM에 대해 우선 알아보도록 하자.
APM은 .NET framework 1.0 시절부터 나온 닷넷의 비동기 프로그래밍 기술 중 원조격 기술인데 지금은 TAP에 밀려 거의 사용되지 않는다. 코드를 작성하다보면 아래와 같이 Begin ~ 와 End ~ 로 쌍을 이루는 함수들을 본적 있을 것이다.
이러한 함수들이 바로 APM을 위한 함수들인데 Begin ~ 함수와 End ~ 함수는 쌍으로 동작해야 한다.
사용법은 크게 두가지가 있다.
1. 완료포트(Completion Port) 를 이용한 사용법
2. WaitOne을 이용한 사용법
우선 완료포트를 이용한 사용법부터 설명해보면
Begin ~ 함수 호출할때 완료포트를 지정해주면 함수가 비동기적으로 동작하다 작업이 완료되면 완료포트로 지정된 함수가 호출된다.
그리고 완료포트에서 나머지 작업을 처리하고 End ~ 함수를 호출하는 형태인데, 대략적으로 아래와 같다
// BING 에서 만들어준 코드임
public static void Main()
{
// Create an instance of FileStream that will be used to read the file.
FileStream fs = new FileStream("test.txt", FileMode.Open, FileAccess.Read, FileShare.Read, 1024,
FileOptions.Asynchronous);
// Create a state object that will contain information about the read operation.
State state = new State();
state.FileStream = fs;
// Begin reading data asynchronously from the file stream.
// 여기서 비동기적으로 파일 읽기 시작함
// 완료되면 파라메터로 건네 준 EndReadCallback 함수가 호출됨
fs.BeginRead(state.Buffer, 0, state.Buffer.Length,
new AsyncCallback(EndReadCallback), state);
}
// 파일 읽기 작업 완료되면 이 함수가 호출됨
public static void EndReadCallback(IAsyncResult asyncResult)
{
// Get the state object that was passed to the callback method.
State state = (State)asyncResult.AsyncState;
// Get the number of bytes read from the file stream.
// 여기서 EndRead 함수를 호출함으로써 BeginRead함수와 쌍을 맞춰줌
int bytesRead = state.FileStream.EndRead(asyncResult);
if (bytesRead > 0)
{
// Do something with the data...
Console.WriteLine(Encoding.ASCII.GetString(state.Buffer));
// Continue reading data asynchronously from the file stream.
state.FileStream.BeginRead(state.Buffer, 0, state.Buffer.Length,
new AsyncCallback(EndReadCallback), state);
}
}
class State
{
public byte[] Buffer = new byte[1024];
public FileStream FileStream;
}
보기만해도 토나올거 같은 코드긴 하다. 동기식으로 작성하면 3~5줄이면 될건데 비동기식으로 작성하니 코드량이 폭발적으로 늘어났다. 알다시피 코드량이 길고 복잡하면 코드유지보수비용이 폭발적으로 증가하는데 코드 유지보수관점에서 보면 결코 좋은 코드는 아니다.
두번째로 WaitOne을 사용한 방법이 있는데 코드 자체는 이쪽이 더 간단해지기는 한다.
흐름부터 살펴보면 Begin ~ 함수를 호출해서 비동기적으로 실행시킨 후에 다른 작업들을 진행하다가 Begin ~ 에서 진행 중인 작업의 결과를 알아야 다음으로 진행할 수 있는 때가 오면 WaitOne 함수를 통해 Begin ~ 함수가 완료될때까지 대기 한후에 완료되면 End~ 함수를 호출하고 결과값을 가지고 계속해서 진행하는 방식이다.
코드로 살펴보면 아래와 같다.
public static void Main()
{
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
// 여기서 비동기로 connect 작업이 진행됨
IAsyncResult result = socket.BeginConnect(host, port, (ar) =>
{
}, null);
// 비동기로 진행되는 동안 다른 여러가지 일을 할 수 있음
// 다른 할일 다 하고 Connect 결과가 필요하다면 connect 작업이 완료될때까지 대기함
result.AsyncWaitHandle.WaitOne(TimeSpan.FromMilliseconds(timeout), false);
try
{
// connect 작업이 완료되면 EndConnect을 호출해줌
socket.EndConnect(result);
break;
}
catch { }
continue;
}
이러한 흐름은 사실 TAP과 비슷한데... End ~ 함수를 호출해야 한다는 제약 조건이 빠지고 async await 기능이 추가되면 그게 바로 TAP이긴 하다.
APM이 똥같긴 하지만 현대의 TAP 기술이 구축되는데 큰 아이디어를 제공한 것만은 명백해보인다.
그럼 이제 요약하고 포스팅을 마무리하도록 하자.
우선 위 코드들을 보면 알겠지만 매우 더럽다.
APM이 더 이상 쓰이지 않는 이유이기도 한데, 알다시피 코드가 더러우면 가독성이 낮아지고 코드 유지보수비용이 증가한다. 근데 이뿐인가? Begin ~ 함수와 End ~ 함수를 쌍으로 맞춰야 한다는 제약으로 인해 휴먼에러가 발생할 여지까지 있다. TAP이 없던 시절이라면 모를까 TAP이라는 대안이 있는 현 시대에서는 사용할 필요가 없어보인다.
만약 기존의 코드가 APM으로 되어 있어서 어쩔수 없다?
요즘 시대가 얼마나 좋은 시대인가 아래와 같이 BING AI 님에게 APM 코드 던져주면서 TAP 코드로 바꿔달라고 하면 바꿔주니 AI 님에게 문의하여 TAP 코드로 마이그레이션 하는 방법을 추천한다.
이 방법이 초창기에는 마이그레이션 비용이 클지 모르나 유지보수비용 관점에서 접근해보면 마이그레이션 이후 한달만 지나도 본전은 다 뽑을 것이라 본다.
알아야 할 중요한 기술도 많으니 이미 구식기술이 되어버린 APM에 대해선 여기까지만 다루도록 하자... 어차피 TAP이 있는 한 쓸일도 없을테니...
'C#' 카테고리의 다른 글
[C#] is 패턴 매칭, is not 패턴 매칭 에 대해 알아보자 (0) | 2023.11.18 |
---|---|
[C#] 비동기 프로그래밍: Task.Run과 바로 호출의 차이점 (2) | 2023.10.24 |
[C#] 디버깅 시 데이터의 조사식 포맷을 변경해보자 (0) | 2022.12.30 |
[C#] 열거형 값을 문자열로 사용해보자 (0) | 2022.12.30 |
[C#] 외부함수를 멤버함수처럼 사용하기 (0) | 2022.12.26 |