Saltar al pie de página
.NET AYUDA

Lista concurrente en C# (Cómo funciona para desarrolladores)

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 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:

using System.Collections.Concurrent;

ConcurrentDictionary<string, string> recommendedBooks = new ConcurrentDictionary<string, string>();
using System.Collections.Concurrent;

ConcurrentDictionary<string, string> recommendedBooks = new ConcurrentDictionary<string, string>();
Imports System.Collections.Concurrent

Private recommendedBooks As New ConcurrentDictionary(Of String, String)()
$vbLabelText   $csharpLabel

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

public void Insert(string user, string book)
{
    // Try to add the book to the user's recommendations
    recommendedBooks.TryAdd(user, book);
}
public void Insert(string user, string book)
{
    // Try to add the book to the user's recommendations
    recommendedBooks.TryAdd(user, book);
}
Public Sub Insert(ByVal user As String, ByVal book As String)
	' Try to add the book to the user's recommendations
	recommendedBooks.TryAdd(user, book)
End Sub
$vbLabelText   $csharpLabel

In this scenario, the 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 TryRemove method:

public void RemoveAt(string user)
{
    // Attempt to remove the book for the specified user
    string removedBook;
    recommendedBooks.TryRemove(user, out removedBook);
}
public void RemoveAt(string user)
{
    // Attempt to remove the book for the specified user
    string removedBook;
    recommendedBooks.TryRemove(user, out removedBook);
}
Public Sub RemoveAt(ByVal user As String)
	' Attempt to remove the book for the specified user
	Dim removedBook As String = Nothing
	recommendedBooks.TryRemove(user, removedBook)
End Sub
$vbLabelText   $csharpLabel

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()
{
    // Create an array to hold the recommended books
    string[] bookArray = new string[recommendedBooks.Count];

    // Copy the values of the concurrent dictionary to the array
    recommendedBooks.Values.CopyTo(bookArray, 0);
}
public void CopyTo()
{
    // Create an array to hold the recommended books
    string[] bookArray = new string[recommendedBooks.Count];

    // Copy the values of the concurrent dictionary to the array
    recommendedBooks.Values.CopyTo(bookArray, 0);
}
Public Sub CopyTo()
	' Create an array to hold the recommended books
	Dim bookArray(recommendedBooks.Count - 1) As String

	' Copy the values of the concurrent dictionary to the array
	recommendedBooks.Values.CopyTo(bookArray, 0)
End Sub
$vbLabelText   $csharpLabel

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.

Learn More About IronPDF is a popular C# library that allows you to generate PDF documents from HTML effortlessly.

using IronPdf;

class Program
{
    static void Main(string[] args)
    {
        var renderer = new ChromePdfRenderer();

        // 1. Convert HTML String to PDF
        var htmlContent = "<h1>Hello, IronPDF!</h1><p>This is a PDF from an HTML string.</p>";
        var pdfFromHtmlString = renderer.RenderHtmlAsPdf(htmlContent);
        pdfFromHtmlString.SaveAs("HTMLStringToPDF.pdf");

        // 2. Convert HTML File to PDF
        var htmlFilePath = "path_to_your_html_file.html"; // Specify the path to your HTML file
        var pdfFromHtmlFile = renderer.RenderHtmlFileAsPdf(htmlFilePath);
        pdfFromHtmlFile.SaveAs("HTMLFileToPDF.pdf");

        // 3. Convert URL to PDF
        var url = "http://ironpdf.com"; // Specify the URL
        var pdfFromUrl = renderer.RenderUrlAsPdf(url);
        pdfFromUrl.SaveAs("URLToPDF.pdf");
    }
}
using IronPdf;

class Program
{
    static void Main(string[] args)
    {
        var renderer = new ChromePdfRenderer();

        // 1. Convert HTML String to PDF
        var htmlContent = "<h1>Hello, IronPDF!</h1><p>This is a PDF from an HTML string.</p>";
        var pdfFromHtmlString = renderer.RenderHtmlAsPdf(htmlContent);
        pdfFromHtmlString.SaveAs("HTMLStringToPDF.pdf");

        // 2. Convert HTML File to PDF
        var htmlFilePath = "path_to_your_html_file.html"; // Specify the path to your HTML file
        var pdfFromHtmlFile = renderer.RenderHtmlFileAsPdf(htmlFilePath);
        pdfFromHtmlFile.SaveAs("HTMLFileToPDF.pdf");

        // 3. Convert URL to PDF
        var url = "http://ironpdf.com"; // Specify the URL
        var pdfFromUrl = renderer.RenderUrlAsPdf(url);
        pdfFromUrl.SaveAs("URLToPDF.pdf");
    }
}
Imports IronPdf

Friend Class Program
	Shared Sub Main(ByVal args() As String)
		Dim renderer = New ChromePdfRenderer()

		' 1. Convert HTML String to PDF
		Dim htmlContent = "<h1>Hello, IronPDF!</h1><p>This is a PDF from an HTML string.</p>"
		Dim pdfFromHtmlString = renderer.RenderHtmlAsPdf(htmlContent)
		pdfFromHtmlString.SaveAs("HTMLStringToPDF.pdf")

		' 2. Convert HTML File to PDF
		Dim htmlFilePath = "path_to_your_html_file.html" ' Specify the path to your HTML file
		Dim pdfFromHtmlFile = renderer.RenderHtmlFileAsPdf(htmlFilePath)
		pdfFromHtmlFile.SaveAs("HTMLFileToPDF.pdf")

		' 3. Convert URL to PDF
		Dim url = "http://ironpdf.com" ' Specify the URL
		Dim pdfFromUrl = renderer.RenderUrlAsPdf(url)
		pdfFromUrl.SaveAs("URLToPDF.pdf")
	End Sub
End Class
$vbLabelText   $csharpLabel

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<int, string> processedData = new ConcurrentDictionary<int, string>();

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

// Process your data concurrently and store the results in the dictionary
Parallel.ForEach(dataList, (dataItem) =>
{
    // Process each data item and add the result to the dictionary
    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<DataItem> GetDataList()
{
    List<DataItem> dataList = new List<DataItem>()
    {
        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<int, string> processedData)
{
    string html = "<h1>Processed Data Report</h1><ul>";
    foreach (var kvp in processedData)
    {
        html += $"<li>Item {kvp.Key}: {kvp.Value}</li>";
    }
    html += "</ul>";
    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<int, string> processedData = new ConcurrentDictionary<int, string>();

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

// Process your data concurrently and store the results in the dictionary
Parallel.ForEach(dataList, (dataItem) =>
{
    // Process each data item and add the result to the dictionary
    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<DataItem> GetDataList()
{
    List<DataItem> dataList = new List<DataItem>()
    {
        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<int, string> processedData)
{
    string html = "<h1>Processed Data Report</h1><ul>";
    foreach (var kvp in processedData)
    {
        html += $"<li>Item {kvp.Key}: {kvp.Value}</li>";
    }
    html += "</ul>";
    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(Of Integer, String)()

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

' Process your data concurrently and store the results in the dictionary
Parallel.ForEach(dataList, Sub(dataItem)
	' Process each data item and add the result to the dictionary
	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(Of DataItem) GetDataList()
'{
'	List<DataItem> dataList = New List<DataItem>() { 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(Of int, string) processedData)
'{
'	string html = "<h1>Processed Data Report</h1><ul>";
'	foreach (var kvp in processedData)
'	{
'		html += string.Format("<li>Item {0}: {1}</li>", kvp.Key, kvp.Value);
'	}
'	html += "</ul>";
'	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
'}
$vbLabelText   $csharpLabel

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 of its library for HTML to PDF conversion, allowing you to explore its capabilities, and license options starting from $799.

Preguntas Frecuentes

¿Qué son las colecciones concurrentes en C#?

Las colecciones concurrentes en C# son un conjunto de clases de colecciones genéricas seguras para hilos que aseguran la seguridad de los hilos cuando múltiples hilos acceden a recursos compartidos.

¿Por qué es importante la seguridad de los hilos en C#?

La seguridad de los hilos es crítica en C# para evitar el caos y la corrupción de datos cuando múltiples hilos acceden y modifican recursos compartidos simultáneamente. Asegura que las operaciones se ejecuten de una manera controlada.

¿Cómo puedo crear una lista segura para hilos en C#?

Aunque C# no proporciona directamente una clase List segura para hilos, puedes usar otras colecciones concurrentes como `ConcurrentBag` o `ConcurrentDictionary` para operaciones seguras similares.

¿Qué es un ConcurrentDictionary en C#?

Un ConcurrentDictionary en C# es una clase de colección segura para hilos dentro del espacio de nombres `System.Collections.Concurrent`. Permite que múltiples hilos añadan, actualicen y eliminen pares clave-valor de manera segura.

¿Cómo asegura un ConcurrentDictionary la seguridad de los hilos?

Un ConcurrentDictionary asegura la seguridad de los hilos manejando internamente la sincronización, permitiendo que solo un hilo realice operaciones como agregar o eliminar ítems a la vez.

¿Cómo puedes añadir un elemento a un ConcurrentDictionary?

Puedes añadir un elemento a un ConcurrentDictionary usando el método TryAdd, que intenta añadir un par clave-valor solo si la clave no existe ya en el diccionario.

¿Cuál es el propósito del método CopyTo en las colecciones concurrentes?

El método CopyTo en colecciones concurrentes se utiliza para copiar los elementos de la colección en un array, proporcionando una manera de transferir datos de la colección a otro formato de almacenamiento.

¿Puede utilizarse IronPDF para generar informes PDF a partir de datos procesados?

Sí, IronPDF puede ser utilizado para generar informes PDF a partir de datos procesados por aplicaciones con múltiples hilos, capturando los resultados de las operaciones concurrentes.

¿Cómo mejora el uso de IronPDF la funcionalidad de las operaciones concurrentes?

IronPDF mejora las operaciones concurrentes al permitir la creación de documentos PDF a partir de datos procesados, ofreciendo una manera de documentar y compartir los resultados del procesamiento multihilos.

¿Qué papel juega IronPDF en aplicaciones C# multihilo?

IronPDF permite a los desarrolladores generar informes PDF a partir de datos procesados en paralelo, facilitando la consolidación y el intercambio de resultados de operaciones multihilo.

Curtis Chau
Escritor Técnico

Curtis Chau tiene una licenciatura en Ciencias de la Computación (Carleton University) y se especializa en el desarrollo front-end con experiencia en Node.js, TypeScript, JavaScript y React. Apasionado por crear interfaces de usuario intuitivas y estéticamente agradables, disfruta trabajando con frameworks modernos y creando manuales bien ...

Leer más