방프리
24.01.07 Chapter3. 태스크 기반 비동기 프로그래밍 (Item 29) 본문
Item 29 : 동기, 비동기 메서드를 함께 사용해서는 안 된다.
비동기 메서드와 동기 메서드를 사용할 때 두 가지 규칙을 지켜야 한다.
1. 비동기 작업이 완료될 때까지 기다리는 동기 메서드를 만들지 말라.
2. 수행 시간이 오래 걸리는 CPU 중식 작업을 비동기로 수행하지 말라.
위 규칙을 지키지 않으면 교착상태 (DeadLock)에 빠지기 쉽다.
public static async Task<int> ComputeUsageAsync()
{
try
{
var operand = await GetLeftOperandForIndex(19);
var operand2 = await GetRightOperandForIndex(23);
return operand + operand2;
}
catch (KeyNotFoundException e)
{
return 0;
}
}
public static int ComputeUsage()
{
try
{
var operand = GetLeftOperandForIndex(19).Result;
var operand2 = GetLeftOperandForIndex(23).Result;
return operand + operand2;
}
catch (AggregateException e)
when (e.InnerExceptions.FirstOrDefault().GetType()
== typeof(KeyNotFoundException))
{
return 0;
}
}
await 구문을 사용하는 로직이 가독성이 훨씬 올라갈 뿐더러, 예외에 대한 대처도 가능하다.
만약 Result 혹은 Task.Wait()에서 예외가 발생했을 땐 AggregateException 예외를 일단 던진 다음 프로그래머는
AggregateException 내부의 예외를 통해서 정확한 이슈를 파악해야 한다.
private static async Task SimulatedWorkAsync()
{
await Task.Delay(1000);
}
public static void SyncOverAsyncDeadlock()
{
var delayTask = SimulatedWorkAsync();
delayTask.Wait();
}
위의 코드는 애플리케이션의 종류에 따라 교착상태에 빠질 수 있다. 각 애플리케이션마다 SynchronizationContext의
동작 방식이 다르기 때문이다.
(Console에서는 SynchronizationContext는 Thread Pool에서 여러 Thread를 가져오지만 GUI 또는 ASP.NET에서는
하나의 Thread만 가져올 수 있기 때문)
저자는 Main 메서드에서 대해서도 주의점을 이야기 했지만 C# 7.1부터는 Main()도 async를 지원하기 때문에
대략적으로만 알아두자
나는 이 항목이 기존 동기 메서드를 비동기 메서드로 변경하는 상황에 처했을 때 쉽게 해결하기 위한 임시 방편 코드를
넣는 걸 생각하는 개발자들에게 조언한다 생각했다.
private int GetUserSilver()
{
var userCached = _cached.Key("user");
return userCached.Monetary;
}
private int GetUserMonetaryByType(MonetaryType type)
{
return type switch
{
case MonetaryType.Silver => GetUserSilver(),
_ => 0,
};
}
//위의 MonetaryType에서 한 타입의 경우 동시성 이슈 때문에 DB에서 Get 해야함
private int GetUserSilver()
{
var userCached = _cached.Key("user");
return userCached.Monetary;
}
private async Task<int> GroupMonetary()
{
var query = "...";
var dbConn = ...
var result = await dbConn.GetResult();
//...
return result;
}
//이렇게 코딩하지 말자.
private int GetUserMonetaryByType(MonetaryType type)
{
var result = 0;
switch (type)
{
case MonetaryType.Silver:
result = GetUserSilver();
break;
case MonetaryType.GroupCoin:
result = GroupMonetary().Result;
Task.Delay(1000); //Task가 언제 끝날지 모르지만 대략 이때 정도에는 끝나겠지라는 생각
break;
default:
throw new Exception("Not Supported type");
break;
}
return result;
}
'C# > More Effective C#' 카테고리의 다른 글
24.02.13 Chapter3. 태스크 기반 비동기 프로그래밍 (Item 31) (1) | 2024.02.13 |
---|---|
24.01.12 Chapter3. 태스크 기반 비동기 프로그래밍 (Item 30) (2) | 2024.01.12 |
24.01.04 Chapter3. 태스크 기반 비동기 프로그래밍 (Item 28) (0) | 2024.01.04 |
24.01.04 Chapter3. 태스크 기반 비동기 프로그래밍 (Item 27) (1) | 2024.01.04 |
24.01.02 Chapter2. API 설계 (Item 26) (0) | 2024.01.02 |
Comments