본문 바로가기

IT

C# Queue 사용법 예제

왜 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<stringdynamic>();
                        if (waitQueue.TryDequeue(out tempDic))
                        {
                            saveList.Add(tempDic);
                        }
                    }

                    // 100개 저장 
                    int cntRun await InsertDBTableAsync(saveListyyyyMMddHHmmssproc_nmkey);

                    if (cntRun > 0)
                    {
                        cntTot += cntRun;
                        saveList.Clear();
                    }
                }
            }

            int remainCnt waitQueue.Count;
            // 나머지 저장
            if (remainCnt > 0)
            {
                for (int i = 0; i < remainCnt; i++)
                {
                    tempDic new Dictionary<stringdynamic>();
                    if (waitQueue.TryDequeue(out tempDic))
                    {
                        saveList.Add(tempDic);
                    }
                }

                int cntRun await InsertDBTableAsync(saveListyyyyMMddHHmmssproc_nmkey);  // await  비동기 저장

                if (cntRun > 0)
                {
                    cntTot += cntRun;
                    saveList.Clear();
                }
            }

            return cntTot;
        }

 

핵심 목적

List<Dictionary<string, dynamic>> 형태의 데이터를 100개씩 나눠서,
비동기 처리(InsertDBTableAsync)로 저장하고,
마지막에 남은 잔여 데이터도 저장하는 로직입니다.

 

왜 이렇게 나눠 저장할까?

  • 대량 데이터를 한 번에 저장하면:
    • 성능 저하
    • 트랜잭션 부하
    • 타임아웃 위험
  • 그래서 "배치 단위(여기선 100개)"로 나눠 저장하는 겁니다.

 

☞  그래서 흐름은 이렇게 됩니다.

  1. 여러 소스(예: 센서, 사용자 입력 등)에서 동시에 데이터를 큐에 넣음
  2. waitQueue.Enqueue(dic);
  3. 한쪽에서 일정 수량(100개)만큼 모이면하나씩 꺼내서 저장 리스트에 담고 DB 저장
  4. waitQueue.TryDequeue(out tempDic);
  5. 마지막 남은 데이터까지 처리

 

 

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는 큐이기 때문에 아래는 안 됩니다:

var item = queue[1]; // 불가능
 
 
 

그래서 실무에서는 보통 이렇게 생각하고 설계합니다.

  • ☑️ 먼저 넣은 데이터부터 처리해야 할 때
    → Queue<T> 또는 ConcurrentQueue<T> 사용
    → 예: 로그 처리, 메시지 큐, 대기열, 백그라운드 작업 처리 등
  • ❌ 특정 인덱스를 바로 꺼내야 하는 구조면
    → List<T> 또는 Dictionary<T, V> 같은 다른 컬렉션 사용

 

☆ 왜 List로 안 하고 ConcurrentQueue?


List<T> ❌ 위험 ❌ 부적합 ✅ 가능
ConcurrentQueue<T> ✅ 안전 ✅ 적합 ❌ 불가 (FIFO 전용)

 

☆ 결론

맞습니다. ConcurrentQueue.Enqueue(dic) 하는 이유는
멀티스레드에서 안전하게 큐에 담아두고,
100개씩 모아서 효율적으로 배치 처리하기 위해서입니다.