Skip to main content

.NET Queues

Introduction

A queue is a fundamental data structure that follows the First-In-First-Out (FIFO) principle. This means that the first element added to the queue will be the first one to be removed, much like a line of people waiting for a service. In .NET, the Queue<T> class provides a powerful and type-safe implementation of this data structure.

Queues are useful in many scenarios where you need to process items in the order they were received, such as:

  • Print job management
  • Task scheduling
  • Message processing systems
  • Breadth-first search algorithms
  • Buffer management

In this guide, we'll explore how to work with the Queue<T> class in .NET, understand its key operations, and see practical examples of queues in action.

Creating a Queue

The Queue<T> class is a generic collection, which means you can create queues that hold any type of object. Here's how to create a queue:

csharp
// Create an empty queue of integers
Queue<int> numberQueue = new Queue<int>();

// Create a queue with initial values
Queue<string> fruitQueue = new Queue<string>(new[] { "apple", "orange", "banana" });

Basic Queue Operations

Adding Items (Enqueue)

To add an item to a queue, use the Enqueue method:

csharp
Queue<string> customerQueue = new Queue<string>();

// Add customers to the queue
customerQueue.Enqueue("Alice");
customerQueue.Enqueue("Bob");
customerQueue.Enqueue("Charlie");

Console.WriteLine($"Number of customers in queue: {customerQueue.Count}");

Output:

Number of customers in queue: 3

Removing Items (Dequeue)

To remove and return the item at the beginning of the queue, use the Dequeue method:

csharp
Queue<string> customerQueue = new Queue<string>();
customerQueue.Enqueue("Alice");
customerQueue.Enqueue("Bob");
customerQueue.Enqueue("Charlie");

// Serve the first customer
string firstCustomer = customerQueue.Dequeue();
Console.WriteLine($"Now serving: {firstCustomer}");
Console.WriteLine($"Remaining customers in queue: {customerQueue.Count}");

Output:

Now serving: Alice
Remaining customers in queue: 2

Peeking at the First Item

If you want to see the next item without removing it, use the Peek method:

csharp
Queue<string> customerQueue = new Queue<string>();
customerQueue.Enqueue("Alice");
customerQueue.Enqueue("Bob");

// See who's next without removing them
string nextCustomer = customerQueue.Peek();
Console.WriteLine($"Next customer: {nextCustomer}");
Console.WriteLine($"Customers in queue: {customerQueue.Count}");

Output:

Next customer: Alice
Customers in queue: 2

Checking if a Queue Contains an Item

You can check if a queue contains a specific item using the Contains method:

csharp
Queue<string> customerQueue = new Queue<string>();
customerQueue.Enqueue("Alice");
customerQueue.Enqueue("Bob");

bool hasAlice = customerQueue.Contains("Alice");
bool hasCharlie = customerQueue.Contains("Charlie");

Console.WriteLine($"Is Alice in the queue? {hasAlice}");
Console.WriteLine($"Is Charlie in the queue? {hasCharlie}");

Output:

Is Alice in the queue? True
Is Charlie in the queue? False

Clearing a Queue

To remove all items from a queue, use the Clear method:

csharp
Queue<string> customerQueue = new Queue<string>();
customerQueue.Enqueue("Alice");
customerQueue.Enqueue("Bob");

Console.WriteLine($"Customers before clearing: {customerQueue.Count}");
customerQueue.Clear();
Console.WriteLine($"Customers after clearing: {customerQueue.Count}");

Output:

Customers before clearing: 2
Customers after clearing: 0

Iterating Through a Queue

You can iterate through a queue without removing items using a foreach loop:

csharp
Queue<string> fruitQueue = new Queue<string>(new[] { "apple", "orange", "banana" });

Console.WriteLine("Fruits in the queue:");
foreach (string fruit in fruitQueue)
{
Console.WriteLine(fruit);
}

// The queue remains unchanged
Console.WriteLine($"Items still in queue: {fruitQueue.Count}");

Output:

Fruits in the queue:
apple
orange
banana
Items still in queue: 3

Converting Queue to Array or List

Sometimes you might want to convert your queue to an array or list:

csharp
Queue<int> numberQueue = new Queue<int>(new[] { 1, 2, 3, 4, 5 });

// Convert to array
int[] numberArray = numberQueue.ToArray();
Console.WriteLine("Queue as array: " + string.Join(", ", numberArray));

// Convert to list
List<int> numberList = new List<int>(numberQueue);
Console.WriteLine("Queue as list: " + string.Join(", ", numberList));

Output:

Queue as array: 1, 2, 3, 4, 5
Queue as list: 1, 2, 3, 4, 5

Real-World Applications

Let's look at some practical applications of queues in real-world scenarios:

Example 1: Print Job Management

csharp
public class PrintJob
{
public string DocumentName { get; set; }
public int Pages { get; set; }
public string User { get; set; }

public override string ToString()
{
return $"{DocumentName} ({Pages} pages) - Submitted by {User}";
}
}

public class PrintServer
{
private Queue<PrintJob> jobQueue = new Queue<PrintJob>();

public void AddJob(PrintJob job)
{
jobQueue.Enqueue(job);
Console.WriteLine($"Job added: {job}");
Console.WriteLine($"Current queue length: {jobQueue.Count} job(s)");
}

public void ProcessNextJob()
{
if (jobQueue.Count > 0)
{
PrintJob job = jobQueue.Dequeue();
Console.WriteLine($"Processing job: {job}");
Console.WriteLine($"Remaining jobs: {jobQueue.Count}");
}
else
{
Console.WriteLine("No jobs to process.");
}
}
}

// Usage:
PrintServer server = new PrintServer();
server.AddJob(new PrintJob { DocumentName = "Report.pdf", Pages = 5, User = "Alice" });
server.AddJob(new PrintJob { DocumentName = "Presentation.pptx", Pages = 20, User = "Bob" });
server.ProcessNextJob();
server.ProcessNextJob();
server.ProcessNextJob();

Output:

Job added: Report.pdf (5 pages) - Submitted by Alice
Current queue length: 1 job(s)
Job added: Presentation.pptx (20 pages) - Submitted by Bob
Current queue length: 2 job(s)
Processing job: Report.pdf (5 pages) - Submitted by Alice
Remaining jobs: 1
Processing job: Presentation.pptx (20 pages) - Submitted by Bob
Remaining jobs: 0
No jobs to process.

Example 2: Message Processing System

csharp
public class Message
{
public string Content { get; set; }
public DateTime Timestamp { get; set; }
public string Sender { get; set; }
}

public class MessageProcessor
{
private Queue<Message> messageQueue = new Queue<Message>();
private System.Timers.Timer processingTimer;

public MessageProcessor()
{
// Set up a timer to process messages every 2 seconds
processingTimer = new System.Timers.Timer(2000);
processingTimer.Elapsed += ProcessMessages;
}

public void Start()
{
Console.WriteLine("Message processor started");
processingTimer.Start();
}

public void Stop()
{
processingTimer.Stop();
Console.WriteLine("Message processor stopped");
}

public void EnqueueMessage(Message message)
{
messageQueue.Enqueue(message);
Console.WriteLine($"Message received from {message.Sender}: {message.Content}");
}

private void ProcessMessages(object sender, System.Timers.ElapsedEventArgs e)
{
Console.WriteLine($"\nProcessing messages at {DateTime.Now}");
Console.WriteLine($"Messages in queue: {messageQueue.Count}");

// Process up to 3 messages at a time
int messagesToProcess = Math.Min(3, messageQueue.Count);

for (int i = 0; i < messagesToProcess; i++)
{
if (messageQueue.Count > 0)
{
Message message = messageQueue.Dequeue();
Console.WriteLine($"Processing: [{message.Timestamp}] {message.Sender}: {message.Content}");
}
}

Console.WriteLine($"Messages remaining: {messageQueue.Count}");
}
}

// Example usage (in a real application this would be better with async/await):
void RunMessageExample()
{
MessageProcessor processor = new MessageProcessor();
processor.Start();

// Add some messages
processor.EnqueueMessage(new Message {
Content = "Hello!",
Sender = "Alice",
Timestamp = DateTime.Now
});

// Simulate message arrival over time
System.Threading.Thread.Sleep(1000);

processor.EnqueueMessage(new Message {
Content = "How are you?",
Sender = "Bob",
Timestamp = DateTime.Now
});

processor.EnqueueMessage(new Message {
Content = "Meeting at 3pm",
Sender = "Charlie",
Timestamp = DateTime.Now
});

System.Threading.Thread.Sleep(3000); // Wait for processing

processor.EnqueueMessage(new Message {
Content = "Don't forget to submit the report",
Sender = "Dave",
Timestamp = DateTime.Now
});

System.Threading.Thread.Sleep(3000); // Wait for processing

processor.Stop();
}

This example simulates a message processing system that collects messages and processes them in batches, demonstrating how queues can be used for asynchronous processing.

Thread-Safe Queues with ConcurrentQueue

In multi-threaded applications, you might need a thread-safe queue. .NET provides the ConcurrentQueue<T> class in the System.Collections.Concurrent namespace for this purpose:

csharp
using System.Collections.Concurrent;

// Create a thread-safe queue
ConcurrentQueue<int> concurrentQueue = new ConcurrentQueue<int>();

// Add items
concurrentQueue.Enqueue(1);
concurrentQueue.Enqueue(2);
concurrentQueue.Enqueue(3);

// Try to get the first item without removing it
if (concurrentQueue.TryPeek(out int peekedValue))
{
Console.WriteLine($"Next item: {peekedValue}");
}

// Try to remove and return the first item
if (concurrentQueue.TryDequeue(out int dequeuedValue))
{
Console.WriteLine($"Dequeued: {dequeuedValue}");
}

Console.WriteLine($"Items remaining: {concurrentQueue.Count}");

Output:

Next item: 1
Dequeued: 1
Items remaining: 2

Note that ConcurrentQueue<T> uses TryDequeue and TryPeek methods instead of Dequeue and Peek to safely handle concurrent operations.

Performance Considerations

When working with queues, consider these performance characteristics:

  • Enqueue and Dequeue operations: Both have O(1) time complexity, making queues very efficient for their intended use case.
  • Search operations: The Contains method has O(n) time complexity as it must scan the entire queue.
  • Memory overhead: Queues maintain references to all items, so be mindful of memory usage with large queues.
  • Resizing: The internal array might need to resize as items are added, causing occasional performance hits.

For very large queues or performance-critical applications, consider using specialized implementations or the concurrent collections.

Summary

In this guide, we've explored .NET queues through the Queue<T> class and how it implements the FIFO (First-In-First-Out) principle. We've learned about:

  • Creating queues and populating them with initial data
  • Basic operations like Enqueue, Dequeue, and Peek
  • Checking for items and clearing queues
  • Iterating through queue elements
  • Converting queues to other collection types
  • Real-world applications of queues
  • Thread-safe queues using ConcurrentQueue<T>
  • Performance considerations when working with queues

Queues are versatile data structures that prove invaluable in many programming scenarios where order of processing matters. Whether you're building a print spooler, message processor, or task scheduler, understanding how to effectively use queues will enhance your .NET applications.

Exercises

  1. Create a simple task scheduler that uses a queue to process tasks in the order they are added.
  2. Implement a basic customer service system where clients are served in the order they arrive.
  3. Build a breadth-first search algorithm using a queue to traverse a tree or graph.
  4. Create a producer-consumer scenario with multiple threads adding to and removing from a ConcurrentQueue<T>.
  5. Implement a circular buffer using a queue with a maximum size, where adding new items removes the oldest ones when full.

Additional Resources



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)