首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> C# >> 關於C#

C# Queue 線程安全:Synchronized 包裝與自訂鎖最佳實踐

     編輯:關於C#

問題:Queue 非線程安全

Queue (非泛型) 與 Queue<T> (泛型) 皆非線程安全。多執行緒同時 Enqueue/Dequeue 或枚舉時修改,會導致資料損毀或 InvalidOperationException。

非泛型 Queue:內建 Synchronized 包裝


var queue = new Queue();
queue.Enqueue("A"); queue.Enqueue("B");

var syncQueue = Queue.Synchronized(queue); // 回傳同步包裝器
Console.WriteLine($"Original: {queue.IsSynchronized}");     // False
Console.WriteLine($"Wrapped: {syncQueue.IsSynchronized}");   // True

// 使用:單一操作原子化
syncQueue.Enqueue("C");
var item = syncQueue.Dequeue();

// 枚舉仍需手動鎖定!
lock (syncQueue.SyncRoot)
{
    foreach (var x in syncQueue) Console.WriteLine(x);
}

泛型 Queue<T>:無內建同步,需自訂鎖


var queue = new Queue();
private static readonly object _lock = new object();

// 生產者
Task.Run(() => {
    for (int i = 0; i < 100; i++)
    {
        lock (_lock) queue.Enqueue($"Item-{i}");
        Thread.Sleep(10);
    }
});

// 消費者
Task.Run(() => {
    while (true)
    {
        string item = null;
        lock (_lock) 
        {
            if (queue.Count > 0) item = queue.Dequeue();
        }
        if (item != null) Console.WriteLine($"Processed: {item}");
        else Thread.Sleep(50);
    }
});

進階:生產者/消費者模式完整實作


using System.Collections.Concurrent;
using System.Threading;

public class BoundedQueue
{
    private readonly ConcurrentQueue _queue = new();
    private readonly SemaphoreSlim _slots; // 可用空位
    private readonly SemaphoreSlim _items; // 可用項目

    public BoundedQueue(int capacity)
    {
        _slots = new SemaphoreSlim(capacity, capacity);
        _items = new SemaphoreSlim(0, capacity);
    }

    public void Enqueue(T item)
    {
        _slots.Wait(); // 等待空位
        _queue.Enqueue(item);
        _items.Release(); // 通知有新項目
    }

    public T Dequeue()
    {
        _items.Wait(); // 等待項目
        _queue.TryDequeue(out var item);
        _slots.Release(); // 釋放空位
        return item;
    }

    public bool TryDequeue(out T item, TimeSpan timeout)
    {
        if (_items.Wait(timeout))
        {
            _queue.TryDequeue(out item);
            _slots.Release();
            return true;
        }
        item = default;
        return false;
    }
}

.NET 內建線程安全佇列對比

類別特性適用場景
ConcurrentQueue<T>無鎖、高併發 Enqueue/TryDequeue、無界高吞吐、無界緩衝、多生產多消費
BlockingCollection<T>包裝 ConcurrentQueue、支援界限/阻塞/CompleteAdding生產者消費者、背壓控制、優雅關閉
Channel<T> (.NET Core 3.0+)非同步優先、支援等待/取消/彈性緩衝非同步管道、高效能資料流
Queue<T> + lock簡單、可控、支援 foreach (需鎖)低併發、需自訂邏輯、教學用途
Queue.Synchronized單一方法原子、枚舉仍需手動鎖舊代碼遷移、簡單場景

選擇指南

  1. 新專案、高併發、非同步:Channel<T> 或 BlockingCollection<T>
  2. 簡單同步、低併發:lock + Queue<T>
  3. 需有界、背壓、完整 Adding:BlockingCollection<T>
  4. 維護舊代碼 (非泛型):**Queue.Synchronized**
  5. 避免:自行實作無鎖佇列 (極難正確)

常見錯誤

  • 以為 Queue.Synchronized 保護枚舉 → 不保護,必須 lock (syncQueue.SyncRoot)
  • ConcurrentQueue 用 Count 判斷再 TryDequeue → 競態條件,直接 TryDequeue
  • BlockingCollection 未呼叫 CompleteAdding() → 消費者永遠阻塞
  • 跨執行緒 foreach 未鎖定 → InvalidOperationException

最佳實踐範本


// 生產者消費者標準模式 (.NET 4.0+)
var queue = new BlockingCollection(boundedCapacity: 1000);

// 生產者 (多執行緒)
Parallel.For(0, 100, i => queue.Add(new WorkItem(i)));

// 完成生產
queue.CompleteAdding();

// 消費者 (單/多執行緒)
foreach (var item in queue.GetConsumingEnumerable())
{
    Process(item);
}

理解 Queue 線程安全本質,選對工具,才能寫出高效、正確的併發程式。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved