C# Concurrent List (How it Works for Developers)

If you've ever had multiple threads jostling for access to a shared resource, you know that thread safe implementation is not a game. Don't worry, though! C# has you covered with concurrent collections - a powerful suite of thread safe, generic collection classes that ensure thread safety with style and grace.

Thread Safety and Concurrent Collections in C#

Let's start by picturing a bustling city intersection with no traffic lights. You can imagine the chaos! This is similar to what happens when multiple threads concurrently access a shared resource without a proper system in place. Thankfully, in C#, we have traffic lights for our threads - these are called concurrent collections. They are collection classes that allow only one same thread to access a resource at a time. This thread safety is crucial when working with multiple threads.

Exploring Concurrent Thread Safe Collections in C#

In C#, the namespace System.Collections.Concurrent has a variety of concurrent collection classes, like ConcurrentDictionary, ConcurrentQueue, ConcurrentStack, and ConcurrentBag. These unordered collection classes provide a thread safe version of their non-concurrent counterparts. What sets concurrent collections apart is that they are unordered concurrent collections, meaning elements do not have a specific order. For instance, with a concurrent list, you don't know exactly where an item is inserted. The focus is on ensuring thread safety, not on maintaining an order.

Let's take a real-life example. Think of a password submit post on a website. With a concurrent collection, multiple users can submit their passwords simultaneously. Each 'submit' action is like a thread, and the concurrent collection ensures that each submission is thread safe, processed safely and effectively.

ConcurrentDictionary A Real-World Example

Now, let's explore the ConcurrentDictionary collection class with a real-life example. Picture an online bookstore with a recommendation feature. Each user's click adds a book to their personal recommendation list, represented by a dictionary. As multiple users browse and click books at the same time, we have multiple threads concurrently accessing the dictionary.

A ConcurrentDictionary in C# would look something like this

ConcurrentDictionary recommendedBooks = new ConcurrentDictionary();
ConcurrentDictionary recommendedBooks = new ConcurrentDictionary();
Dim recommendedBooks As New ConcurrentDictionary()
VB   C#

To add a book to a user's entire collection of recommendations, we could use the Insert method:

public void Insert(string user, string book)
{
    recommendedBooks.TryAdd(user, book);
}
public void Insert(string user, string book)
{
    recommendedBooks.TryAdd(user, book);
}
Public Sub Insert(ByVal user As String, ByVal book As String)
	recommendedBooks.TryAdd(user, book)
End Sub
VB   C#

In this scenario, ConcurrentDictionary collection class ensures that every click (or 'thread') is dealt with individually, so no two users' recommendations get mixed up. It handles all the thread safety, so you don't have to worry about data races and other concurrency issues related to multiple threads.

Implementing Thread Safe Operations

Other than TryAdd, concurrent collections in C# provide a variety of other thread safe operations like TryRemove and TryUpdate. These methods ensure that only one thread can perform an operation at a time. So, for instance, if we wanted to remove a book from a user's recommendations in the previous example, we could use the RemoveAt method

public void RemoveAt(string user)
{
    string removedBook;
    recommendedBooks.TryRemove(user, out removedBook);
}
public void RemoveAt(string user)
{
    string removedBook;
    recommendedBooks.TryRemove(user, out removedBook);
}
Public Sub RemoveAt(ByVal user As String)
	Dim removedBook As String = Nothing
	recommendedBooks.TryRemove(user, removedBook)
End Sub
VB   C#

The TryRemove method will attempt to remove the value of the provided key (in this case, a user) and put it into the removedBook variable.

Copying Concurrent Collections

Now, let's say you want to copy your concurrent collection to an array. Concurrent collections provide a CopyTo method for this exact purpose:

public void CopyTo()
{
    string[] bookArray = new string[recommendedBooks.Count];
    recommendedBooks.Values.CopyTo(bookArray, 0);
}
public void CopyTo()
{
    string[] bookArray = new string[recommendedBooks.Count];
    recommendedBooks.Values.CopyTo(bookArray, 0);
}
Public Sub CopyTo()
	Dim bookArray(recommendedBooks.Count - 1) As String
	recommendedBooks.Values.CopyTo(bookArray, 0)
End Sub
VB   C#

Here, the CopyTo method copies all the books (values) from the recommendedBooks concurrent dictionary into the bookArray.

Thread Safe Collection

C# also provides thread safe collections, which are designed to ensure safe access to shared resources in multithreaded environments. These collections, such as ConcurrentBag, ConcurrentQueue, and ConcurrentStack, offer thread-safe implementations where multiple threads can access and modify the collection concurrently without causing conflicts or data corruption.

They guarantee consistency and integrity by handling the synchronization internally, making them ideal for scenarios where an unordered collection is sufficient, and thread safety is of utmost importance in your C# applications.

IronPDF is a popular C# library that allows you to generate PDF documents from HTML effortlessly. While it may not seem directly related to concurrent lists at first, IronPDF can complement your concurrent collection operations by providing an easy way to create PDF reports, logs, or any other document that captures the results of your concurrent processing.

Consider the scenario where you have a multithreaded application that performs intensive data processing. As the threads work their magic on the data, you might want to capture the results and generate a PDF report for further analysis or record-keeping. This is where IronPDF comes into play.

Using IronPDF is as simple as adding the library to your project and utilizing its convenient API. Here's an example of how you can integrate IronPDF with your concurrent collection operations:

using IronPdf;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading.Tasks;

// Create a concurrent dictionary to hold your processed data
ConcurrentDictionary processedData = new ConcurrentDictionary();

// Define your data list (replace with your actual data source)
List dataList = GetDataList();

// Process your data concurrently and store the results in the dictionary
Parallel.ForEach(dataList, (dataItem) =>
{
    string processedResult = ProcessDataItem(dataItem);
    processedData.TryAdd(dataItem.Id, processedResult);
});

// Generate a PDF report with the processed data
var renderer = new ChromePdfRenderer();
var pdfDocument = renderer.RenderHtmlAsPdf(BuildHtmlReport(processedData));
pdfDocument.SaveAs("C:\\processed_data_report.pdf");

// Method to retrieve the data list (replace with your actual data source logic)
List GetDataList()
{
    List dataList = new List()
    {
        new DataItem { Id = 1, Name = "Item 1" },
        new DataItem { Id = 2, Name = "Item 2" },
        new DataItem { Id = 3, Name = "Item 3" },
        new DataItem { Id = 4, Name = "Item 4" }
    };
    return dataList;
}

// Method to process each data item and return the result (replace with your actual data processing logic)
string ProcessDataItem(DataItem dataItem)
{
    // Simulating data processing with a delay
    Task.Delay(100).Wait();
    return $"Processed: {dataItem.Name}";
}

// Method to build the HTML report using the processed data (replace with your actual reporting logic)
string BuildHtmlReport(ConcurrentDictionary processedData)
{
    string html = "Processed Data Report";
    foreach (var kvp in processedData)
    {
        html += $"Item {kvp.Key}: {kvp.Value}";
    }
    html += "";
    return html;
}

// Placeholder class for your data item (replace with your actual data item class)
public class DataItem
{
    public int Id { get; set; }
    public string Name { get; set; }
    // Add other properties as needed
}
using IronPdf;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading.Tasks;

// Create a concurrent dictionary to hold your processed data
ConcurrentDictionary processedData = new ConcurrentDictionary();

// Define your data list (replace with your actual data source)
List dataList = GetDataList();

// Process your data concurrently and store the results in the dictionary
Parallel.ForEach(dataList, (dataItem) =>
{
    string processedResult = ProcessDataItem(dataItem);
    processedData.TryAdd(dataItem.Id, processedResult);
});

// Generate a PDF report with the processed data
var renderer = new ChromePdfRenderer();
var pdfDocument = renderer.RenderHtmlAsPdf(BuildHtmlReport(processedData));
pdfDocument.SaveAs("C:\\processed_data_report.pdf");

// Method to retrieve the data list (replace with your actual data source logic)
List GetDataList()
{
    List dataList = new List()
    {
        new DataItem { Id = 1, Name = "Item 1" },
        new DataItem { Id = 2, Name = "Item 2" },
        new DataItem { Id = 3, Name = "Item 3" },
        new DataItem { Id = 4, Name = "Item 4" }
    };
    return dataList;
}

// Method to process each data item and return the result (replace with your actual data processing logic)
string ProcessDataItem(DataItem dataItem)
{
    // Simulating data processing with a delay
    Task.Delay(100).Wait();
    return $"Processed: {dataItem.Name}";
}

// Method to build the HTML report using the processed data (replace with your actual reporting logic)
string BuildHtmlReport(ConcurrentDictionary processedData)
{
    string html = "Processed Data Report";
    foreach (var kvp in processedData)
    {
        html += $"Item {kvp.Key}: {kvp.Value}";
    }
    html += "";
    return html;
}

// Placeholder class for your data item (replace with your actual data item class)
public class DataItem
{
    public int Id { get; set; }
    public string Name { get; set; }
    // Add other properties as needed
}
Imports IronPdf
Imports System.Collections.Concurrent
Imports System.Collections.Generic
Imports System.Threading.Tasks

' Create a concurrent dictionary to hold your processed data
Private processedData As New ConcurrentDictionary()

' Define your data list (replace with your actual data source)
Private dataList As List = GetDataList()

' Process your data concurrently and store the results in the dictionary
Parallel.ForEach(dataList, Sub(dataItem)
	Dim processedResult As String = ProcessDataItem(dataItem)
	processedData.TryAdd(dataItem.Id, processedResult)
End Sub)

' Generate a PDF report with the processed data
Dim renderer = New ChromePdfRenderer()
Dim pdfDocument = renderer.RenderHtmlAsPdf(BuildHtmlReport(processedData))
pdfDocument.SaveAs("C:\processed_data_report.pdf")

' Method to retrieve the data list (replace with your actual data source logic)
'INSTANT VB TODO TASK: Local functions are not converted by Instant VB:
'List GetDataList()
'{
'	List dataList = New List() { New DataItem { Id = 1, Name = "Item 1" }, New DataItem { Id = 2, Name = "Item 2" }, New DataItem { Id = 3, Name = "Item 3" }, New DataItem { Id = 4, Name = "Item 4" } };
'	Return dataList;
'}

' Method to process each data item and return the result (replace with your actual data processing logic)
'INSTANT VB TODO TASK: Local functions are not converted by Instant VB:
'string ProcessDataItem(DataItem dataItem)
'{
'	' Simulating data processing with a delay
'	Task.Delay(100).Wait();
'	Return string.Format("Processed: {0}", dataItem.Name);
'}

' Method to build the HTML report using the processed data (replace with your actual reporting logic)
'INSTANT VB TODO TASK: Local functions are not converted by Instant VB:
'string BuildHtmlReport(ConcurrentDictionary processedData)
'{
'	string html = "Processed Data Report";
'	foreach (var kvp in processedData)
'	{
'		html += string.Format("Item {0}: {1}", kvp.Key, kvp.Value);
'	}
'	html += "";
'	Return html;
'}

' Placeholder class for your data item (replace with your actual data item class)
'INSTANT VB TODO TASK: Local functions are not converted by Instant VB:
'public class DataItem
'{
'	public int Id
'	{
'		get;
'		set;
'	}
'	public string Name
'	{
'		get;
'		set;
'	}
'	' Add other properties as needed
'}
VB   C#

Here is the output of the code:

C# Concurrent List (How It Works For Developers) Figure 1 - Output

Conclusion

In conclusion, understanding and utilizing C# concurrent collections, such as concurrent lists, can greatly enhance your ability to handle multi-threading scenarios and ensure thread safety in your applications. With concurrent collections, you can manage shared resources effectively, preventing data races and collisions between threads.

Integrating external libraries like IronPDF can further augment the functionality of concurrent collections by enabling the generation of visually appealing PDF reports or documents. IronPDF offers a free trial, allowing you to explore its capabilities, and license options starting from $749.