Đây là cách triển khai rất cơ bản của một BlockingCollection
hỗ trợ đang chờ, với rất nhiều tính năng còn thiếu. Nó sử dụng AsyncEnumerable
thư viện, giúp liệt kê không đồng bộ có thể cho các phiên bản C # cũ hơn 8.0.
public class AsyncBlockingCollection<T>
{
private Queue<T> _queue = new Queue<T>();
private SemaphoreSlim _semaphore = new SemaphoreSlim(0);
private int _consumersCount = 0;
private bool _isAddingCompleted;
public void Add(T item)
{
lock (_queue)
{
if (_isAddingCompleted) throw new InvalidOperationException();
_queue.Enqueue(item);
}
_semaphore.Release();
}
public void CompleteAdding()
{
lock (_queue)
{
if (_isAddingCompleted) return;
_isAddingCompleted = true;
if (_consumersCount > 0) _semaphore.Release(_consumersCount);
}
}
public IAsyncEnumerable<T> GetConsumingEnumerable()
{
lock (_queue) _consumersCount++;
return new AsyncEnumerable<T>(async yield =>
{
while (true)
{
lock (_queue)
{
if (_queue.Count == 0 && _isAddingCompleted) break;
}
await _semaphore.WaitAsync();
bool hasItem;
T item = default;
lock (_queue)
{
hasItem = _queue.Count > 0;
if (hasItem) item = _queue.Dequeue();
}
if (hasItem) await yield.ReturnAsync(item);
}
});
}
}
Ví dụ sử dụng:
var abc = new AsyncBlockingCollection<int>();
var producer = Task.Run(async () =>
{
for (int i = 1; i <= 10; i++)
{
await Task.Delay(100);
abc.Add(i);
}
abc.CompleteAdding();
});
var consumer = Task.Run(async () =>
{
await abc.GetConsumingEnumerable().ForEachAsync(async item =>
{
await Task.Delay(200);
await Console.Out.WriteAsync(item + " ");
});
});
await Task.WhenAll(producer, consumer);
Đầu ra:
1 2 3 4 5 6 7 8 9 10
Cập nhật: Với việc phát hành C # 8, liệt kê không đồng bộ đã trở thành một tính năng ngôn ngữ tích hợp. Các lớp bắt buộc ( IAsyncEnumerable
, IAsyncEnumerator
) được nhúng trong .NET Core 3.0 và được cung cấp dưới dạng gói cho .NET Framework 4.6.1+ ( Microsoft.Bcl.AsyncInterfaces ).
Đây là một GetConsumingEnumerable
triển khai thay thế , có cú pháp C # 8 mới:
public async IAsyncEnumerable<T> GetConsumingEnumerable()
{
lock (_queue) _consumersCount++;
while (true)
{
lock (_queue)
{
if (_queue.Count == 0 && _isAddingCompleted) break;
}
await _semaphore.WaitAsync();
bool hasItem;
T item = default;
lock (_queue)
{
hasItem = _queue.Count > 0;
if (hasItem) item = _queue.Dequeue();
}
if (hasItem) yield return item;
}
}
Lưu ý sự cùng tồn tại của await
vàyield
trong cùng một phương pháp.
Ví dụ sử dụng (C # 8):
var consumer = Task.Run(async () =>
{
await foreach (var item in abc.GetConsumingEnumerable())
{
await Task.Delay(200);
await Console.Out.WriteAsync(item + " ");
}
});
Lưu ý await
trước khi foreach
.
await Task.Run(() => blockingCollection.Take())
, tác vụ sẽ được thực hiện trên chuỗi khác và chuỗi giao diện người dùng của bạn sẽ không bị chặn.