Spargine is a collection of open-source assemblies and NuGet packages designed for .NET 10, which I have been developing and maintaining since the release of .NET Framework 2. These assemblies are not only a core part of my projects but are also actively deployed in production environments across several companies I collaborate with.
Get Spargine
You can access the source code and NuGet packages here:
- GitHub: Spargine 10
- NuGet: dotNetDaveNuGet
As asynchronous programming continues to dominate modern .NET development, one notable gap remains: there is still no built-in, async-first queue abstraction in the framework. While types like ConcurrentQueue<T> handle thread safety, they fall short when it comes to true asynchronous producer/consumer workflows.
To address this gap, Spargine introduces ChannelQueue<T>, a lightweight, thread-safe, and high-performance async queue built on top of .NET’s Channel<T> infrastructure. Available in the DotNetTips.Spargine.Core NuGet package, ChannelQueue<T> is designed to simplify async data pipelines, background processing, and producer/consumer scenarios—without forcing you to reinvent the wheel. Whether you’re building parallel pipelines, background services, or event-driven workflows, ChannelQueue<T> provides a clean, extensible abstraction with first-class support for cancellation tokens, bounded capacity, idempotent writes, and batch operations.
Why ChannelQueue<T>?
In a worlIn an ecosystem where async/await is the norm, it’s surprising that developers still have to stitch together their own async queue patterns. ChannelQueue<T> was created to close that gap and deliver a production-ready solution with the following capabilities:
- Fully thread-safe design
- Native async read and write operations
- Support for both bounded and unbounded queues
- Graceful shutdown via cancellation tokens
- Optional locking to prevent further writes
- Batch enqueue support
- Built-in idempotency and deduplication
Under the hood, ChannelQueue<T> leverages the high-performance Channel<T> type, ensuring excellent throughput, low contention, and predictable behavior under load.
Constructors
ChannelQueue<T> provides several constructors to support different capacity and cancellation requirements:
- ChannelQueue()
Initializes a queue with unbounded capacity. - ChannelQueue(int capacity)
Initializes a queue with a fixed bounded capacity. - ChannelQueue(int capacity, TimeSpan? cancellationTimeout)
Creates a bounded queue with an optional timeout for cancellation. - ChannelQueue(TimeSpan? cancellationTimeout)
Creates an unbounded queue with an optional cancellation timeout.
Key Methods
The API surface is intentionally focused and expressive, covering the most common async queue scenarios.
- Acknowledge(string idempotencyKey)
Marks the item associated with the specified idempotency key as processed, allowing future enqueues using the same key. - Clear()
Removes all items from the queue. - ListenAsync(CancellationToken cancellationToken)
Asynchronously streams all queued items until the channel is completed or cancelled. - Lock()
Seals the queue, preventing any further writes. This operation is irreversible and is typically used to signal completion. - ReadAsync(CancellationToken cancellationToken)
Reads a single item asynchronously from the queue. - ReadAsync(Func<T, string> keyResolver, CancellationToken cancellationToken)
Reads an item and removes its associated idempotency key using the provided resolver. Use this overload when the key can be derived from the item itself. - TryRead(out T item)
Attempts to read an item immediately without waiting. - TryWrite(T item)
Attempts to enqueue an item immediately without waiting. - TryWriteOnce(T item, string idempotencyKey, TimeSpan? dedupeWindow)
Attempts to enqueue an item only once per idempotency key. Returns false if the key already exists (and has not been acknowledged or expired) or if the channel cannot accept new items. - WriteAsync(IEnumerable<T> items, bool lockQueue, CancellationToken cancellationToken)
Enqueues a batch of items asynchronously and optionally locks the queue after completion. - WriteAsync(T item, CancellationToken cancellationToken)
Enqueues a single item asynchronously. - WriteOnceAsync(T item, string idempotencyKey, TimeSpan? dedupeWindow, CancellationToken cancellationToken)
Asynchronously enqueues an item only once per idempotency key, providing safe deduplication in async workflows.
Properties
- Completion
Gets a task that completes when the channel is done. - Count
Returns the current number of items in the channel. - IsCompleted
Gets a value indicating whether the channel is completed.
Usage Examples
Writing and Reading a Single Item
var queue = new ChannelQueue<string>();
string item = "Hello, Channel!";
using var cts = new CancellationTokenSource();
await queue.WriteAsync(item, cts.Token);
string dequeued = await queue.ReadAsync();
Writing and Listening to a Stream
var queue = new ChannelQueue<string>();
await queue.WriteAsync("First");
await queue.WriteAsync("Second");
await queue.WriteAsync("Third");
queue.Lock(); // Signal no more writes
await foreach (var item in queue.ListenAsync())
{
Console.WriteLine(item);
}
Summary
If your .NET applications rely on asynchronous workflows and you need a reliable, high-performance queuing mechanism, ChannelQueue<T> from Spargine is a strong fit. It removes boilerplate, simplifies async producer/consumer patterns, and gives you precise control over how data flows through your application.
By combining the power of Channel<T> with a clean, developer-friendly API, ChannelQueue<T> helps you build faster, safer, and more maintainable async systems—without unnecessary complexity.
Get Involved!
The success of open-source projects like Spargine relies on community contributions. If you find these updates useful or have ideas for further improvements, I encourage you to contribute by:
- Submitting pull requests
- Reporting issues
- Suggesting new features
Your input is invaluable in making Spargine an even more powerful tool for the .NET community.
If you are interested in contributing or have any questions, feel free to contact me via email at dotnetdave@live.com. Your support and collaboration are greatly appreciated!
Thank you, and happy coding!
Pick up any books by David McCarter by going to Amazon.com: http://bit.ly/RockYourCodeBooks
Make a one-time donation
Make a monthly donation
Make a yearly donation
Choose an amount
Or enter a custom amount
Your contribution is appreciated.
Your contribution is appreciated.
Your contribution is appreciated.
DonateDonate monthlyDonate yearlyIf you liked this article, please buy David a cup of Coffee by going here: https://www.buymeacoffee.com/dotnetdave
© The information in this article is copywritten and cannot be preproduced in any way without express permission from David McCarter.
Discover more from dotNetTips.com
Subscribe to get the latest posts sent to your email.

