Boost Your .NET Projects with Spargine: Harness the Power of Async Queues with ChannelQueue

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:

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

One-Time
Monthly
Yearly

Make a one-time donation

Make a monthly donation

Make a yearly donation

Choose an amount

$5.00
$15.00
$100.00
$5.00
$15.00
$100.00
$5.00
$15.00
$100.00

Or enter a custom amount

$

Your contribution is appreciated.

Your contribution is appreciated.

Your contribution is appreciated.

DonateDonate monthlyDonate yearly

If 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.

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.