왜 ConcurrentQueue를 쓰고 Enqueue/TryDequeue 하는가?
C# 개발을 하다보면 대부분 배열로 데이터를
저장하고 싶을때 List <>를 많이 사용한다.
List 배열은 인덱스로 데이터를 추출해
가공도 쉽게 할 수 있기 때문이다.
하지만 Queue 를 쓰는 경우도 볼 수 있다.
C#에서 Queue 를 사용하는 경우는 어떤 경우일까?
비전검사, 설비데이터, IoT 센서등의
데이터를 저장하거나 처리해야하는 경우
데이터는 실시간 쏟아지는데
DB는 천천히 처리해야 할 때
👉 목적 1: 스레드 안전(Thread-Safe)
- ConcurrentQueue<T>는 멀티스레드 환경에서도 안전하게 데이터 입출력이 가능.
- 여러 작업이 동시에 큐에 데이터를 넣고 꺼내도 충돌 없이 동작.
👉 목적 2: 대기큐에 담아 100개씩 묶어 처리(Batch 처리)
- 대량 데이터를 한 번에 처리하면 성능 저하나 타임아웃 문제가 발생할 수 있기 때문에
- 100개씩 나눠서 처리하는 구조(callCount)로 설계.
※ 비동기 저장 Queue 예제 (비전검사)
private async Task<int> DMCSaveAsync(List<Dictionary<string, dynamic>> list, string key)
{
ConcurrentQueue<Dictionary<string, dynamic>> waitQueue =
new ConcurrentQueue<Dictionary<string, dynamic>>(); // 큐: 저장 대기 목록 (쓰레드 안전)
Dictionary<string, dynamic> tempDic = new Dictionary<string, dynamic>();
List<Dictionary<string, dynamic>> saveList = new List<Dictionary<string, dynamic>>();
string yyyyMMddHHmmss = DateTime.Now.ToString("yyyyMMddHHmmss");
string proc_nm = string.Empty;
int cntTot = 0;
int callCount = 100;
foreach (Dictionary<string, dynamic> dic in list) // list를 순회하면서 100개씩 대기 큐에 넣음
{
waitQueue.Enqueue(dic);
// 지정한 행의 갯수만큼 데이터를 저장한다
if (waitQueue.Count > callCount) // 일정량(callCount) 초과되면 saveList로 100개 꺼내고 DB 저장
{
for (int i = 0; i < callCount; i++)
{
tempDic = new Dictionary<string, dynamic>();
if (waitQueue.TryDequeue(out tempDic))
{
saveList.Add(tempDic);
}
}
// 100개 저장
int cntRun = await InsertDBTableAsync(saveList, yyyyMMddHHmmss, proc_nm, key);
if (cntRun > 0)
{
cntTot += cntRun;
saveList.Clear();
}
}
}
int remainCnt = waitQueue.Count;
// 나머지 저장
if (remainCnt > 0)
{
for (int i = 0; i < remainCnt; i++)
{
tempDic = new Dictionary<string, dynamic>();
if (waitQueue.TryDequeue(out tempDic))
{
saveList.Add(tempDic);
}
}
int cntRun = await InsertDBTableAsync(saveList, yyyyMMddHHmmss, proc_nm, key); // await 비동기 저장
if (cntRun > 0)
{
cntTot += cntRun;
saveList.Clear();
}
}
return cntTot;
}
핵심 목적
List<Dictionary<string, dynamic>> 형태의 데이터를 100개씩 나눠서,
비동기 처리(InsertDBTableAsync)로 저장하고,
마지막에 남은 잔여 데이터도 저장하는 로직입니다.
왜 이렇게 나눠 저장할까?
- 대량 데이터를 한 번에 저장하면:
- 성능 저하
- 트랜잭션 부하
- 타임아웃 위험
- 그래서 "배치 단위(여기선 100개)"로 나눠 저장하는 겁니다.
☞ 그래서 흐름은 이렇게 됩니다.
- 여러 소스(예: 센서, 사용자 입력 등)에서 동시에 데이터를 큐에 넣음
-
waitQueue.Enqueue(dic);
- 한쪽에서 일정 수량(100개)만큼 모이면하나씩 꺼내서 저장 리스트에 담고 DB 저장
-
waitQueue.TryDequeue(out tempDic);
- 마지막 남은 데이터까지 처리
ConcurrentQueue<T>는 FIFO (선입선출) 큐입니다.
👉 즉,
- 먼저 넣은 것부터 먼저 꺼내집니다.
- 인덱스로 꺼내는 건 안 됩니다.
※ 예제
var queue = new ConcurrentQueue<string>();
queue.Enqueue("A");
queue.Enqueue("B");
queue.Enqueue("C");
queue.TryDequeue(out string result1); // "A"
queue.TryDequeue(out string result2); // "B"
queue.TryDequeue(out string result3); // "C"
이처럼 들어간 순서대로 나오게 됩니다.
🚫 인덱스로 꺼내고 싶다면?
ConcurrentQueue는 큐이기 때문에 아래는 안 됩니다:
그래서 실무에서는 보통 이렇게 생각하고 설계합니다.
- ☑️ 먼저 넣은 데이터부터 처리해야 할 때
→ Queue<T> 또는 ConcurrentQueue<T> 사용
→ 예: 로그 처리, 메시지 큐, 대기열, 백그라운드 작업 처리 등 - ❌ 특정 인덱스를 바로 꺼내야 하는 구조면
→ List<T> 또는 Dictionary<T, V> 같은 다른 컬렉션 사용
☆ 왜 List로 안 하고 ConcurrentQueue?
List<T> | ❌ 위험 | ❌ 부적합 | ✅ 가능 |
ConcurrentQueue<T> | ✅ 안전 | ✅ 적합 | ❌ 불가 (FIFO 전용) |
☆ 결론
맞습니다. ConcurrentQueue.Enqueue(dic) 하는 이유는
→ 멀티스레드에서 안전하게 큐에 담아두고,
→ 100개씩 모아서 효율적으로 배치 처리하기 위해서입니다.
'IT' 카테고리의 다른 글
MSSQL 숫자에서 문자, 문자에서 숫자 FORMAT CAST(... AS FLOAT) (0) | 2025.04.08 |
---|---|
C# 비동기 async / await + Task 이해와 예제 (0) | 2025.04.03 |
C# SWICH 문 예제 (0) | 2025.04.02 |
C# 델리게이트 이벤트핸들러 사용 예제 (0) | 2025.03.28 |
C# 델리게이트 이해 및 예제 (0) | 2025.03.28 |