.NET HELP

C# Semaphoreslim (How It Works For Developers)

Published October 23, 2024
Share:

Introduction

Concurrency management is a critical aspect of high-performance applications in C#. It ensures that resources are utilized efficiently while avoiding potential conflicts or performance bottlenecks, so having a lightweight semaphore controls access can be very helpful. This is where SemaphoreSlim comes into play. SemaphoreSlim is a lightweight synchronization primitive that controls resource access, ultimately preventing race conditions and ensuring thread safety.

So what if you wanted to implement this alongside a PDF library to manage PDF generation processes? You might be looking for a powerful PDF library, where IronPDF comes in. IronPDF is a robust PDF generation and manipulation library for .NET developers that can greatly benefit from concurrency management when being used in multi-threaded environments.

If you want to see SemaphoreSlim and IronPDF in action, be sure to read on as we explore the benefits of using SemaphoreSlim and how to integrate it with IronPDF to safely handle concurrent operations, improve performance, and ensure reliable PDF processing.

Understanding SemaphoreSlim in C#

What is SemaphoreSlim?

SemaphoreSlim is a synchronization primitive in .NET that limits the number of threads that can access a particular resource or pool of resources concurrently. It is a lightweight version of the full Semaphore class, designed to work more efficiently in situations where a simpler, faster semaphore is sufficient.

Some benefits of using SemaphoreSlim are that the system overhead is reduced compared to Semaphore, it's ideal for managing limited resources (such as database connections or file access), and it supports asynchronous wait methods, making it well-suited for modern async/await programming patterns.

Code Example of Basic SemaphoreSlim Usage

using System;
using System.Threading;
using System.Threading.Tasks;
class program
{
    // Semaphore count
    private static SemaphoreSlim semaphore = new SemaphoreSlim(3); // Limit to 3 concurrent threads.
    static async Task Main(string[] args)
    {
        // Start tasks that will wait on the semaphore.
        var tasks = new Task[5];
        for (int i = 0; i < tasks.Length; i++)
        {
            tasks[i] = Task.Run(() => AccessResource(i));
        }
        // Simulate some work in the main thread (e.g., initialization).
        Console.WriteLine("Main thread is preparing resources...");
        await Task.Delay(2000);  // Simulate initialization delay.
        // main thread calls release, releases semaphore permits to allow waiting tasks to proceed.
        Console.WriteLine("Main thread releasing semaphore permits...");
        semaphore.Release(2);  // Releases 2 permits, allowing up to 2 tasks to proceed.
        // Wait for all tasks to complete.
        await Task.WhenAll(tasks);
        Console.WriteLine("All tasks completed.");
    }
    static async Task AccessResource(int id)
    {
        Console.WriteLine($"Task {id} waiting to enter...");
        await _semaphore.WaitAsync();
        try
        {
            Console.WriteLine($"current thread successfully entered by Task {id} .");
            await Task.Delay(1000); // Simulate work.
        }
        finally
        {
            Console.WriteLine($"Task {id} releasing.");
            _semaphore.Release();
        }
    }
}
using System;
using System.Threading;
using System.Threading.Tasks;
class program
{
    // Semaphore count
    private static SemaphoreSlim semaphore = new SemaphoreSlim(3); // Limit to 3 concurrent threads.
    static async Task Main(string[] args)
    {
        // Start tasks that will wait on the semaphore.
        var tasks = new Task[5];
        for (int i = 0; i < tasks.Length; i++)
        {
            tasks[i] = Task.Run(() => AccessResource(i));
        }
        // Simulate some work in the main thread (e.g., initialization).
        Console.WriteLine("Main thread is preparing resources...");
        await Task.Delay(2000);  // Simulate initialization delay.
        // main thread calls release, releases semaphore permits to allow waiting tasks to proceed.
        Console.WriteLine("Main thread releasing semaphore permits...");
        semaphore.Release(2);  // Releases 2 permits, allowing up to 2 tasks to proceed.
        // Wait for all tasks to complete.
        await Task.WhenAll(tasks);
        Console.WriteLine("All tasks completed.");
    }
    static async Task AccessResource(int id)
    {
        Console.WriteLine($"Task {id} waiting to enter...");
        await _semaphore.WaitAsync();
        try
        {
            Console.WriteLine($"current thread successfully entered by Task {id} .");
            await Task.Delay(1000); // Simulate work.
        }
        finally
        {
            Console.WriteLine($"Task {id} releasing.");
            _semaphore.Release();
        }
    }
}
Imports System
Imports System.Threading
Imports System.Threading.Tasks
Friend Class program
	' Semaphore count
	Private Shared semaphore As New SemaphoreSlim(3) ' Limit to 3 concurrent threads.
	Shared Async Function Main(ByVal args() As String) As Task
		' Start tasks that will wait on the semaphore.
		Dim tasks = New Task(4){}
		For i As Integer = 0 To tasks.Length - 1
			tasks(i) = Task.Run(Function() AccessResource(i))
		Next i
		' Simulate some work in the main thread (e.g., initialization).
		Console.WriteLine("Main thread is preparing resources...")
		Await Task.Delay(2000) ' Simulate initialization delay.
		' main thread calls release, releases semaphore permits to allow waiting tasks to proceed.
		Console.WriteLine("Main thread releasing semaphore permits...")
		semaphore.Release(2) ' Releases 2 permits, allowing up to 2 tasks to proceed.
		' Wait for all tasks to complete.
		Await Task.WhenAll(tasks)
		Console.WriteLine("All tasks completed.")
	End Function
	Private Shared Async Function AccessResource(ByVal id As Integer) As Task
		Console.WriteLine($"Task {id} waiting to enter...")
		Await _semaphore.WaitAsync()
		Try
			Console.WriteLine($"current thread successfully entered by Task {id} .")
			Await Task.Delay(1000) ' Simulate work.
		Finally
			Console.WriteLine($"Task {id} releasing.")
			_semaphore.Release()
		End Try
	End Function
End Class
VB   C#

During the operation of a program, the semaphore’s count can dynamically reach zero threads when all available permits have been acquired by threads. This state indicates that the maximum allowed concurrent accesses have been reached.

If you wanted, could set the initial and maximum number of threads, starting the initial semaphore count at zero and then using a separate initialization task that increases the semaphore count when the resource is ready, allowing your chosen number of threads to proceed. When the semaphore count is zero, threads will wait when its trying to enter the semaphore, this is referred to as "block waiting".

You could keep track of the previous semaphore count to adjust the semaphore's behavior based on the previous count, you can then manipulate the semaphore accordingly (e.g., by releasing or waiting). As the threads release, the semaphore count is decreased.

Console Output

C# Semaphoreslim (How It Works For Developers): Figure 1

Common Use Cases for SemaphoreSlim

Some common use cases for SemaphoreSlim are:

  • Limiting access to databases or file systems: It prevents overwhelming these resources with too many concurrent requests.
  • Managing thread pools: It can be used to control the number of threads performing a particular operation, improving stability and performance.

Using SemaphoreSlim with IronPDF for Safe Concurrency

Setting up IronPDF in a Multi-threaded environment

To begin using IronPDF in a multi-threaded environment, start by installing the IronPDF NuGet package. You can do this by navigating to tools > NuGet Package Manager > NuGet Package Manager for Solution and searching IronPDF:

C# Semaphoreslim (How It Works For Developers): Figure 2

Or, Alternatively running the following command in the Package Manager Console:

Install-Package IronPdf
Install-Package IronPdf
'INSTANT VB TODO TASK: The following line uses invalid syntax:
'Install-Package IronPdf
VB   C#

To begin using IronPDF in your code, ensure you have placed the `using IronPdf` statement at the top of your code file. For a more in-depth guide to setting up IronPDF in your environment, check out its getting started page.

Controlling Access to PDF Generation with SemaphoreSlim

When you're using SemaphoreSlim, you can effectively control access to PDF generation tasks. This ensures that your application does not attempt to generate too many PDFs simultaneously, which could impact performance or cause failures.

The following example code demonstrates the basic usage of SemaphoreSlim with IronPDF.

using IronPdf;
using System;
using System.Threading;
using System.Threading.Tasks;
using IronPdf.Exceptions;
using System.Net.Http;
using System.Runtime.CompilerServices;
class program
{
    private static SemaphoreSlim _semaphore = new SemaphoreSlim(2); // Limit to 2 concurrent threads.
    static async Task Main(string[] args)
    {
        var tasks = new Task[5];
        for (int i = 0; i < tasks.Length; i++)
        {
            string htmlContent = $"<h1>PDF Document {i}</h1><p>This is a sample PDF content for task {i}.</p>";
            string outputPath = $"output_{i}.pdf";
            // Start multiple tasks to demonstrate controlled concurrency.
            tasks[i] = GeneratePdfAsync(htmlContent, outputPath, i);
        }
        await Task.WhenAll(tasks);
    }
    static async Task GeneratePdfAsync(string htmlContent, string outputPath, int taskId)
    {
        Console.WriteLine($"Task {taskId} is waiting for access...");
        // Wait to enter the semaphore.
        await _semaphore.WaitAsync();
        try
        {
            Console.WriteLine($"Task {taskId} has started PDF generation.");
            ChromePdfRenderer renderer = new ChromePdfRenderer();
            PdfDocument pdf = await renderer.RenderHtmlAsPdfAsync(htmlContent);
            pdf.SaveAs(outputPath);
            Console.WriteLine($"Task {taskId} has completed PDF generation.");
        }
        finally
        {
            // Ensure semaphore is released to allow other tasks to proceed.
            _semaphore.Release();
            Console.WriteLine($"Task {taskId} has released semaphore.");
        }
    }
}
using IronPdf;
using System;
using System.Threading;
using System.Threading.Tasks;
using IronPdf.Exceptions;
using System.Net.Http;
using System.Runtime.CompilerServices;
class program
{
    private static SemaphoreSlim _semaphore = new SemaphoreSlim(2); // Limit to 2 concurrent threads.
    static async Task Main(string[] args)
    {
        var tasks = new Task[5];
        for (int i = 0; i < tasks.Length; i++)
        {
            string htmlContent = $"<h1>PDF Document {i}</h1><p>This is a sample PDF content for task {i}.</p>";
            string outputPath = $"output_{i}.pdf";
            // Start multiple tasks to demonstrate controlled concurrency.
            tasks[i] = GeneratePdfAsync(htmlContent, outputPath, i);
        }
        await Task.WhenAll(tasks);
    }
    static async Task GeneratePdfAsync(string htmlContent, string outputPath, int taskId)
    {
        Console.WriteLine($"Task {taskId} is waiting for access...");
        // Wait to enter the semaphore.
        await _semaphore.WaitAsync();
        try
        {
            Console.WriteLine($"Task {taskId} has started PDF generation.");
            ChromePdfRenderer renderer = new ChromePdfRenderer();
            PdfDocument pdf = await renderer.RenderHtmlAsPdfAsync(htmlContent);
            pdf.SaveAs(outputPath);
            Console.WriteLine($"Task {taskId} has completed PDF generation.");
        }
        finally
        {
            // Ensure semaphore is released to allow other tasks to proceed.
            _semaphore.Release();
            Console.WriteLine($"Task {taskId} has released semaphore.");
        }
    }
}
Imports IronPdf
Imports System
Imports System.Threading
Imports System.Threading.Tasks
Imports IronPdf.Exceptions
Imports System.Net.Http
Imports System.Runtime.CompilerServices
Friend Class program
	Private Shared _semaphore As New SemaphoreSlim(2) ' Limit to 2 concurrent threads.
	Shared Async Function Main(ByVal args() As String) As Task
		Dim tasks = New Task(4){}
		For i As Integer = 0 To tasks.Length - 1
			Dim htmlContent As String = $"<h1>PDF Document {i}</h1><p>This is a sample PDF content for task {i}.</p>"
			Dim outputPath As String = $"output_{i}.pdf"
			' Start multiple tasks to demonstrate controlled concurrency.
			tasks(i) = GeneratePdfAsync(htmlContent, outputPath, i)
		Next i
		Await Task.WhenAll(tasks)
	End Function
	Private Shared Async Function GeneratePdfAsync(ByVal htmlContent As String, ByVal outputPath As String, ByVal taskId As Integer) As Task
		Console.WriteLine($"Task {taskId} is waiting for access...")
		' Wait to enter the semaphore.
		Await _semaphore.WaitAsync()
		Try
			Console.WriteLine($"Task {taskId} has started PDF generation.")
			Dim renderer As New ChromePdfRenderer()
			Dim pdf As PdfDocument = Await renderer.RenderHtmlAsPdfAsync(htmlContent)
			pdf.SaveAs(outputPath)
			Console.WriteLine($"Task {taskId} has completed PDF generation.")
		Finally
			' Ensure semaphore is released to allow other tasks to proceed.
			_semaphore.Release()
			Console.WriteLine($"Task {taskId} has released semaphore.")
		End Try
	End Function
End Class
VB   C#

In this example, we first initialized SemaphoreSlim and set the initial and maximum count of SemaphoreSlim to '2', limiting it to two concurrent PDF generations. We then created a task array which is used to control the number of tasks the program has to do, after which we use a for loop to dynamically create PDFs based on the number of tasks within the task array.

The `WaitAsync()` method is then used to enter the semaphore, and `Release()` is used in the finally block to ensure that the semaphore is always released even if an exception occurs. The console output logs show when each task begins, finishes, and releases the semaphore, this allows you to track the concurrency behavior.

Output Console

C# Semaphoreslim (How It Works For Developers): Figure 3

Output PDF Files

C# Semaphoreslim (How It Works For Developers): Figure 4

Ensuring Thread Safety in PDF Manipulation Tasks

Thread safety is crucial when multiple threads interact with shared resources. In PDF manipulation, SemaphoreSlim ensures that only a defined number of threads can modify PDFs concurrently, preventing race conditions and ensuring consistency. In the following code, we are simulating a scenario where we are adding a watermark to multiple PDFs while ensuring only one operation occurs at a time.

using IronPdf;
using System;
using System.Threading;
using System.Threading.Tasks;
class program
{
    private static SemaphoreSlim _semaphore = new SemaphoreSlim(1);
    static async Task Main(string[] args)
    {
    // Setting array of tasks
        var tasks = new Task[3];
        for (int i = 0; i < tasks.Length; i++)
        {
            string inputPath = $"input_{i}.pdf";  // Input PDF file path
            string outputPath = $"output_{i}.pdf";  // Output PDF file path
            string watermarkText = @"
<img src='https://ironsoftware.com/img/products/ironpdf-logo-text-dotnet.svg'>
<h1>Iron Software</h1>";
            // Start multiple tasks to add watermarks concurrently.
            tasks[i] = AddWatermarkAsync(inputPath, outputPath, watermarkText, i);
        }
        await Task.WhenAll(tasks); // Wait for all tasks to finish.
    }
    static async Task AddWatermarkAsync(string input, string outputPath, string watermark, int taskId)
    {
        Console.WriteLine($"{DateTime.Now:HH:mm:ss} - Task {taskId} is waiting to add a watermark...");
        // Wait to enter the semaphore.
        await _semaphore.WaitAsync();
        try
        {
            Console.WriteLine($"{DateTime.Now:HH:mm:ss} - Task {taskId} is adding a watermark.");
            var pdf = PdfDocument.FromFile(input);
            pdf.ApplyWatermark(watermark); // Add watermark
            pdf.SaveAs(outputPath); // Save the modified PDF
            Console.WriteLine($"{DateTime.Now:HH:mm:ss} - Task {taskId} has completed watermarking.");
        }
        finally
        {
            // Release the semaphore after the task is done.
            _semaphore.Release();
            Console.WriteLine($"{DateTime.Now:HH:mm:ss} - Task {taskId} has released semaphore.");
        }
    }
}
using IronPdf;
using System;
using System.Threading;
using System.Threading.Tasks;
class program
{
    private static SemaphoreSlim _semaphore = new SemaphoreSlim(1);
    static async Task Main(string[] args)
    {
    // Setting array of tasks
        var tasks = new Task[3];
        for (int i = 0; i < tasks.Length; i++)
        {
            string inputPath = $"input_{i}.pdf";  // Input PDF file path
            string outputPath = $"output_{i}.pdf";  // Output PDF file path
            string watermarkText = @"
<img src='https://ironsoftware.com/img/products/ironpdf-logo-text-dotnet.svg'>
<h1>Iron Software</h1>";
            // Start multiple tasks to add watermarks concurrently.
            tasks[i] = AddWatermarkAsync(inputPath, outputPath, watermarkText, i);
        }
        await Task.WhenAll(tasks); // Wait for all tasks to finish.
    }
    static async Task AddWatermarkAsync(string input, string outputPath, string watermark, int taskId)
    {
        Console.WriteLine($"{DateTime.Now:HH:mm:ss} - Task {taskId} is waiting to add a watermark...");
        // Wait to enter the semaphore.
        await _semaphore.WaitAsync();
        try
        {
            Console.WriteLine($"{DateTime.Now:HH:mm:ss} - Task {taskId} is adding a watermark.");
            var pdf = PdfDocument.FromFile(input);
            pdf.ApplyWatermark(watermark); // Add watermark
            pdf.SaveAs(outputPath); // Save the modified PDF
            Console.WriteLine($"{DateTime.Now:HH:mm:ss} - Task {taskId} has completed watermarking.");
        }
        finally
        {
            // Release the semaphore after the task is done.
            _semaphore.Release();
            Console.WriteLine($"{DateTime.Now:HH:mm:ss} - Task {taskId} has released semaphore.");
        }
    }
}
Imports IronPdf
Imports System
Imports System.Threading
Imports System.Threading.Tasks
Friend Class program
	Private Shared _semaphore As New SemaphoreSlim(1)
	Shared Async Function Main(ByVal args() As String) As Task
	' Setting array of tasks
		Dim tasks = New Task(2){}
		For i As Integer = 0 To tasks.Length - 1
			Dim inputPath As String = $"input_{i}.pdf" ' Input PDF file path
			Dim outputPath As String = $"output_{i}.pdf" ' Output PDF file path
			Dim watermarkText As String = "
<img src='https://ironsoftware.com/img/products/ironpdf-logo-text-dotnet.svg'>
<h1>Iron Software</h1>"
			' Start multiple tasks to add watermarks concurrently.
			tasks(i) = AddWatermarkAsync(inputPath, outputPath, watermarkText, i)
		Next i
		Await Task.WhenAll(tasks) ' Wait for all tasks to finish.
	End Function
	Private Shared Async Function AddWatermarkAsync(ByVal input As String, ByVal outputPath As String, ByVal watermark As String, ByVal taskId As Integer) As Task
		Console.WriteLine($"{DateTime.Now:HH:mm:ss} - Task {taskId} is waiting to add a watermark...")
		' Wait to enter the semaphore.
		Await _semaphore.WaitAsync()
		Try
			Console.WriteLine($"{DateTime.Now:HH:mm:ss} - Task {taskId} is adding a watermark.")
			Dim pdf = PdfDocument.FromFile(input)
			pdf.ApplyWatermark(watermark) ' Add watermark
			pdf.SaveAs(outputPath) ' Save the modified PDF
			Console.WriteLine($"{DateTime.Now:HH:mm:ss} - Task {taskId} has completed watermarking.")
		Finally
			' Release the semaphore after the task is done.
			_semaphore.Release()
			Console.WriteLine($"{DateTime.Now:HH:mm:ss} - Task {taskId} has released semaphore.")
		End Try
	End Function
End Class
VB   C#

By setting the semaphore count to 1 using `private static SemaphoreSlim _semaphore = new SemaphoreSlim(1)`, we ensure that only one task can manipulate PDFs at a time.

Console Output

C# Semaphoreslim (How It Works For Developers): Figure 5

Optimizing Performance with SemaphoreSlim and IronPDF

Managing Resource-Intensive Operations

IronPDF excels in handling resource-intensive tasks, such as converting large HTML files to PDFs, and excels at carrying these tasks out in an asynchronous environment. Using SemaphoreSlim to manage these operations ensures that your application remains responsive without losing performance, even under heavy load.

The following example code demonstrates a scenario where we need to limit the number of concurrent large HTML to PDF conversions to avoid overloading system resources.

using IronPdf;
using System;
using System.Threading;
using System.Threading.Tasks;
using IronPdf.Exceptions;
using System.Net.Http;
using System.Runtime.CompilerServices;
class program
{
    // Limit concurrent large PDF conversions to 2.
    private static SemaphoreSlim _semaphore = new SemaphoreSlim(2);
    static async Task Main(string[] args)
    {
        var tasks = new Task[4];
        for (int i = 0; i < tasks.Length; i++)
        {
            string htmlContent = $"<h1>Large Document {i}</h1><p>Content for a large HTML file {i}.</p>";
            string outputPath = $"large_output_{i}.pdf";
            // Start multiple tasks to convert large HTML files to PDFs.
            tasks[i] = ConvertLargeHtmlAsync(htmlContent, outputPath, i);
        }
        await Task.WhenAll(tasks); // Wait for all tasks to finish.
    }
    // Method to convert large HTML to PDF using SemaphoreSlim to control resource usage.
    public static async Task ConvertLargeHtmlAsync(string htmlContent, string outputPath, int taskId)
    {
        Console.WriteLine($"Task {taskId} is waiting to start conversion...");
        // Wait to enter the semaphore.
        await _semaphore.WaitAsync();
        try
        {
            Console.WriteLine($"Task {taskId} is converting large HTML to PDF.");
            var renderer = new ChromePdfRenderer();
            var pdf = await renderer.RenderHtmlAsPdfAsync(htmlContent); // Convert large HTML to PDF
            pdf.SaveAs(outputPath); // Save the PDF file
            Console.WriteLine($"Task {taskId} has completed conversion.");
        }
        finally
        {
            // Ensure the semaphore is released to allow other tasks to proceed.
            _semaphore.Release();
            Console.WriteLine($"Task {taskId} has released semaphore.");
        }
    }
}
using IronPdf;
using System;
using System.Threading;
using System.Threading.Tasks;
using IronPdf.Exceptions;
using System.Net.Http;
using System.Runtime.CompilerServices;
class program
{
    // Limit concurrent large PDF conversions to 2.
    private static SemaphoreSlim _semaphore = new SemaphoreSlim(2);
    static async Task Main(string[] args)
    {
        var tasks = new Task[4];
        for (int i = 0; i < tasks.Length; i++)
        {
            string htmlContent = $"<h1>Large Document {i}</h1><p>Content for a large HTML file {i}.</p>";
            string outputPath = $"large_output_{i}.pdf";
            // Start multiple tasks to convert large HTML files to PDFs.
            tasks[i] = ConvertLargeHtmlAsync(htmlContent, outputPath, i);
        }
        await Task.WhenAll(tasks); // Wait for all tasks to finish.
    }
    // Method to convert large HTML to PDF using SemaphoreSlim to control resource usage.
    public static async Task ConvertLargeHtmlAsync(string htmlContent, string outputPath, int taskId)
    {
        Console.WriteLine($"Task {taskId} is waiting to start conversion...");
        // Wait to enter the semaphore.
        await _semaphore.WaitAsync();
        try
        {
            Console.WriteLine($"Task {taskId} is converting large HTML to PDF.");
            var renderer = new ChromePdfRenderer();
            var pdf = await renderer.RenderHtmlAsPdfAsync(htmlContent); // Convert large HTML to PDF
            pdf.SaveAs(outputPath); // Save the PDF file
            Console.WriteLine($"Task {taskId} has completed conversion.");
        }
        finally
        {
            // Ensure the semaphore is released to allow other tasks to proceed.
            _semaphore.Release();
            Console.WriteLine($"Task {taskId} has released semaphore.");
        }
    }
}
Imports IronPdf
Imports System
Imports System.Threading
Imports System.Threading.Tasks
Imports IronPdf.Exceptions
Imports System.Net.Http
Imports System.Runtime.CompilerServices
Friend Class program
	' Limit concurrent large PDF conversions to 2.
	Private Shared _semaphore As New SemaphoreSlim(2)
	Shared Async Function Main(ByVal args() As String) As Task
		Dim tasks = New Task(3){}
		For i As Integer = 0 To tasks.Length - 1
			Dim htmlContent As String = $"<h1>Large Document {i}</h1><p>Content for a large HTML file {i}.</p>"
			Dim outputPath As String = $"large_output_{i}.pdf"
			' Start multiple tasks to convert large HTML files to PDFs.
			tasks(i) = ConvertLargeHtmlAsync(htmlContent, outputPath, i)
		Next i
		Await Task.WhenAll(tasks) ' Wait for all tasks to finish.
	End Function
	' Method to convert large HTML to PDF using SemaphoreSlim to control resource usage.
	Public Shared Async Function ConvertLargeHtmlAsync(ByVal htmlContent As String, ByVal outputPath As String, ByVal taskId As Integer) As Task
		Console.WriteLine($"Task {taskId} is waiting to start conversion...")
		' Wait to enter the semaphore.
		Await _semaphore.WaitAsync()
		Try
			Console.WriteLine($"Task {taskId} is converting large HTML to PDF.")
			Dim renderer = New ChromePdfRenderer()
			Dim pdf = Await renderer.RenderHtmlAsPdfAsync(htmlContent) ' Convert large HTML to PDF
			pdf.SaveAs(outputPath) ' Save the PDF file
			Console.WriteLine($"Task {taskId} has completed conversion.")
		Finally
			' Ensure the semaphore is released to allow other tasks to proceed.
			_semaphore.Release()
			Console.WriteLine($"Task {taskId} has released semaphore.")
		End Try
	End Function
End Class
VB   C#

When dealing with resource-heavy tasks like converting large HTML files to PDFs, SemaphoreSlim can help balance the load and optimize resource usage. By setting a limit of 2 concurrent operations, we prevent the system from being overwhelmed by resource-intensive PDF generation tasks. This approach helps distribute the workload more evenly, improving overall application performance and stability.

Output image: Files generated with this method

C# Semaphoreslim (How It Works For Developers): Figure 6

Avoiding Deadlocks in Concurrency Management

Deadlocks can occur if semaphores are not released correctly. A good practice to keep in mind is the use of try-finally blocks to ensure that semaphores are released even if an exception occurs, preventing deadlocks and keeping your application running smoothly. Some best practices to remember for avoiding deadlocks include always releasing the semaphore in the finally block, and avoiding using blocking calls like `.wait()` and `.Result` inside your asynchronous code.

using IronPdf;
using System;
using System.Threading;
using System.Threading.Tasks;
using IronPdf.Exceptions;
using System.Net.Http;
using System.Runtime.CompilerServices;
class program
{
    private static SemaphoreSlim _semaphore = new SemaphoreSlim(3);
    static async Task Main(string[] args)
    {
        var tasks = new Task[3];
        for (int i = 0; i < tasks.Length; i++)
        {
            string content = $"<h1>Document {i}</h1><p>Content for PDF {i}.</p>";
            string path = $"safe_output_{i}.pdf";
            // Start multiple tasks to demonstrate deadlock-free semaphore usage.
            tasks[i] = SafePdfTaskAsync(content, path, i);
        }
        await Task.WhenAll(tasks); // Wait for all tasks to finish.
    }
    // Method demonstrating best practices for using SemaphoreSlim to avoid deadlocks.
    public static async Task SafePdfTaskAsync(string content, string path, int taskId)
    {
        Console.WriteLine($"Task {taskId} is waiting to generate PDF...");
        // Wait to enter the semaphore.
        await _semaphore.WaitAsync();
        try
        {
            Console.WriteLine($"Task {taskId} is generating PDF.");
            var renderer = new ChromePdfRenderer();
            var pdf = await renderer.RenderHtmlAsPdfAsync(content); // Render HTML to PDF
            pdf.SaveAs(path); // Save the PDF
            Console.WriteLine($"Task {taskId} has completed PDF generation.");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Task {taskId} encountered an error: {ex.Message}");
        }
        finally
        {
            // Always release the semaphore, even if an error occurs.
            _semaphore.Release();
            Console.WriteLine($"Task {taskId} has released semaphore.");
        }
    }
}
using IronPdf;
using System;
using System.Threading;
using System.Threading.Tasks;
using IronPdf.Exceptions;
using System.Net.Http;
using System.Runtime.CompilerServices;
class program
{
    private static SemaphoreSlim _semaphore = new SemaphoreSlim(3);
    static async Task Main(string[] args)
    {
        var tasks = new Task[3];
        for (int i = 0; i < tasks.Length; i++)
        {
            string content = $"<h1>Document {i}</h1><p>Content for PDF {i}.</p>";
            string path = $"safe_output_{i}.pdf";
            // Start multiple tasks to demonstrate deadlock-free semaphore usage.
            tasks[i] = SafePdfTaskAsync(content, path, i);
        }
        await Task.WhenAll(tasks); // Wait for all tasks to finish.
    }
    // Method demonstrating best practices for using SemaphoreSlim to avoid deadlocks.
    public static async Task SafePdfTaskAsync(string content, string path, int taskId)
    {
        Console.WriteLine($"Task {taskId} is waiting to generate PDF...");
        // Wait to enter the semaphore.
        await _semaphore.WaitAsync();
        try
        {
            Console.WriteLine($"Task {taskId} is generating PDF.");
            var renderer = new ChromePdfRenderer();
            var pdf = await renderer.RenderHtmlAsPdfAsync(content); // Render HTML to PDF
            pdf.SaveAs(path); // Save the PDF
            Console.WriteLine($"Task {taskId} has completed PDF generation.");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Task {taskId} encountered an error: {ex.Message}");
        }
        finally
        {
            // Always release the semaphore, even if an error occurs.
            _semaphore.Release();
            Console.WriteLine($"Task {taskId} has released semaphore.");
        }
    }
}
Imports IronPdf
Imports System
Imports System.Threading
Imports System.Threading.Tasks
Imports IronPdf.Exceptions
Imports System.Net.Http
Imports System.Runtime.CompilerServices
Friend Class program
	Private Shared _semaphore As New SemaphoreSlim(3)
	Shared Async Function Main(ByVal args() As String) As Task
		Dim tasks = New Task(2){}
		For i As Integer = 0 To tasks.Length - 1
			Dim content As String = $"<h1>Document {i}</h1><p>Content for PDF {i}.</p>"
			Dim path As String = $"safe_output_{i}.pdf"
			' Start multiple tasks to demonstrate deadlock-free semaphore usage.
			tasks(i) = SafePdfTaskAsync(content, path, i)
		Next i
		Await Task.WhenAll(tasks) ' Wait for all tasks to finish.
	End Function
	' Method demonstrating best practices for using SemaphoreSlim to avoid deadlocks.
	Public Shared Async Function SafePdfTaskAsync(ByVal content As String, ByVal path As String, ByVal taskId As Integer) As Task
		Console.WriteLine($"Task {taskId} is waiting to generate PDF...")
		' Wait to enter the semaphore.
		Await _semaphore.WaitAsync()
		Try
			Console.WriteLine($"Task {taskId} is generating PDF.")
			Dim renderer = New ChromePdfRenderer()
			Dim pdf = Await renderer.RenderHtmlAsPdfAsync(content) ' Render HTML to PDF
			pdf.SaveAs(path) ' Save the PDF
			Console.WriteLine($"Task {taskId} has completed PDF generation.")
		Catch ex As Exception
			Console.WriteLine($"Task {taskId} encountered an error: {ex.Message}")
		Finally
			' Always release the semaphore, even if an error occurs.
			_semaphore.Release()
			Console.WriteLine($"Task {taskId} has released semaphore.")
		End Try
	End Function
End Class
VB   C#

By using a `try-catch-finally` block, we have ensured that the SemaphoreSlim object is always released, even if an exception is thrown, thus preventing deadlocks. By logging errors and properly managing semaphore releases we can keep the program stable and prevent any unexpected behavior.

As you can see in the output image below, I have simulated an error by trying to make the program load an HTML file that doesn't exist, but even with this error, the program prints the error message which tells me what went wrong and then proceeds to release the semaphore using the finally block.

C# Semaphoreslim (How It Works For Developers): Figure 7

Benefits of Using IronPDF for Concurrent PDF Processing

Efficient and Reliable PDF Processing

IronPDF is designed to handle concurrent PDF processing tasks efficiently, offering performance and reliability superior to many other PDF libraries. Its robust architecture allows it to scale with your application's needs, making it ideal for high-demand environments. When compared to other PDF libraries based off performance, ease-of-use, and robustness criteria, IronPDF proves to be a strong competitor. To showcase this, I have compared IronPDF to several other popular PDF libraries such as iTextSharp, PDFsharp, DinkToPdf, and EvoPDF:

1. Performance

IronPDF:

  • Rendering Speed: IronPDF is known for its fast and efficient rendering capabilities, particularly when converting HTML to PDF. It uses Chrome-based rendering, which provides high fidelity to the original HTML content, including CSS and JavaScript execution.
  • Resource Management: IronPDF is optimized for handling large and complex PDFs with less memory usage compared to other libraries, making it suitable for high-volume applications.
  • Asynchronous Operations: Supports asynchronous PDF generation, allowing for better performance in web applications where responsiveness is crucial. iTextSharp:

  • Rendering Speed: iTextSharp offers good performance for text-heavy PDFs but can slow down significantly with complex layouts or images.
  • Resource Management: Memory usage can be higher with iTextSharp, especially when handling large documents or complex manipulations, leading to performance bottlenecks in some cases. PDFsharp:

  • Rendering Speed: PDFsharp is generally slower compared to IronPDF when dealing with complex layouts or when converting from HTML, as it lacks a native HTML rendering engine.
  • Resource Management: It is less optimized for memory usage and can struggle with large files or documents that contain numerous images. DinkToPdf:

  • Rendering Speed: DinkToPdf uses the wkhtmltopdf engine, which is effective for basic HTML to PDF conversions but may struggle with more complex or dynamic content.
  • Resource Management: It often requires significant memory and processing power, and lacks native support for asynchronous operations, limiting its performance in high-load scenarios. EvoPDF:

  • Rendering Speed: EvoPDF also provides Chrome-based rendering like IronPDF, offering good performance, especially for HTML to PDF conversions.
  • Resource Management: It is well-optimized but might still consume more resources compared to IronPDF in some scenarios due to less aggressive optimizations.

2. Ease of Use

IronPDF:

  • API Design: IronPDF offers a modern, intuitive API that is easy to use for developers of all skill levels. The library is designed to work seamlessly with .NET applications, making it a great choice for C# developers.
  • Documentation and Support: Comprehensive documentation, a large number of code examples, and excellent customer support make it easy to get started and resolve issues quickly.
  • Installation and Integration: Easily installed via NuGet and integrates smoothly into existing .NET projects, requiring minimal configuration. iTextSharp:

  • API Design: iTextSharp has a steep learning curve, with a more complex API that may be overwhelming for beginners. Its flexibility comes at the cost of simplicity.
  • Documentation and Support: While well-documented, the extensive configuration options can make it harder to find straightforward examples for common tasks.
  • Installation and Integration: Available through NuGet, but requires a deeper understanding of the API to integrate effectively. PDFsharp:

  • API Design: PDFsharp is designed to be simple for basic PDF tasks but lacks advanced features out-of-the-box, which can limit its use for more complex scenarios.
  • Documentation and Support: Basic documentation is available, but it is less extensive and lacks detailed examples for advanced usage compared to IronPDF.
  • Installation and Integration: Easy to install via NuGet but offers limited HTML to PDF functionality. DinkToPdf:

  • API Design: DinkToPdf’s API is relatively simple but less polished compared to IronPDF. It mainly targets HTML to PDF conversion and offers fewer features for direct PDF manipulation.
  • Documentation and Support: Documentation is limited, and community support is not as robust as other libraries, making troubleshooting more difficult.
  • Installation and Integration: Can be more complex to install, requiring additional dependencies like wkhtmltopdf, which can complicate setup. EvoPDF:

  • API Design: EvoPDF provides a straightforward API similar to IronPDF, focused heavily on HTML to PDF conversion with ease of use in mind.
  • Documentation and Support: Well-documented with good support options, but not as extensive in community-driven examples as IronPDF.
  • Installation and Integration: Easy to integrate into .NET projects with NuGet packages available.

3. Robustness

IronPDF:

  • Feature Set: IronPDF is highly robust, supporting a wide range of features including HTML to PDF conversion, PDF editing, text extraction, encryption, annotations, and digital signatures.
  • Error Handling: Offers robust error handling and exception management, making it reliable for production environments.
  • Compatibility: Fully compatible with .NET Core, .NET 5+, and legacy .NET Framework versions, making it versatile across different project types. iTextSharp:

  • Feature Set: iTextSharp is extremely robust with a comprehensive feature set that supports almost any PDF task, including complex manipulations and form handling.
  • Error Handling: Good error handling but can be complex to manage due to the library’s intricacies.
  • Compatibility: Well-suited for a wide range of environments, including .NET Framework and .NET Core. PDFsharp:

  • Feature Set: Basic PDF creation and manipulation features. Lacks some advanced features like HTML to PDF conversion and more sophisticated document editing.
  • Error Handling: Basic error handling; is less reliable in complex scenarios compared to more robust libraries like IronPDF.
  • Compatibility: Compatible with .NET Framework and .NET Core, but with limited advanced functionality. DinkToPdf:

  • Feature Set: Primarily focused on HTML to PDF. Limited in terms of direct PDF manipulation and lacks advanced features like annotations and form handling.
  • Error Handling: Basic error handling; prone to crashes or hangs on complex HTML or large files.
  • Compatibility: Works with .NET Core and .NET Framework but requires external dependencies, which can introduce compatibility issues. EvoPDF:

  • Feature Set: Offers a strong set of features similar to IronPDF, including advanced HTML to PDF conversions and some document manipulation capabilities.
  • Error Handling: Robust error handling and reliable performance in production environments.
  • Compatibility: Fully compatible with .NET Core, .NET Framework, and newer .NET versions, making it versatile and reliable.

Summary

  • Performance: IronPDF and EvoPDF lead in performance due to their Chrome-based rendering engines, while iTextSharp and PDFsharp can lag behind in handling complex documents.
  • Ease of Use: IronPDF excels with its intuitive API and extensive documentation, making it accessible for all levels of developers. iTextSharp offers power at the cost of simplicity, while DinkToPdf and PDFsharp are easier but less feature-rich.
  • Robustness: IronPDF and iTextSharp provide the most robust feature sets, with IronPDF offering simpler integration and modern features like async support, while iTextSharp covers more niche use cases with a steeper learning curve.

Comprehensive Support for Asynchronous Programming

IronPDF seamlessly integrates with async programming models, complementing concurrency control mechanisms like SemaphoreSlim. This allows developers to build responsive and performance-friendly applications with minimal effort.

IronPDF also offers extensive documentation and support resources that help developers understand and implement effective error-handling practices. This comprehensive support is valuable for troubleshooting and optimizing PDF operations in .NET projects.

IronPDF offers:

  • Comprehensive Documentation: Extensive and user-friendly documentation covering all features.
  • 24/5 Support: Active engineer support is available.
  • Video Tutorials: Step-by-step video guides are available on YouTube.
  • Community Forum: Engaged community for additional support.
  • PDF API reference: Offers API references so you can get the most out of what our tools have to offer.

For more information, check out IronPDF's extensive documentation.

Conclusion

Using SemaphoreSlim for concurrency management in .NET applications is crucial, especially when dealing with resource-intensive tasks like PDF processing. By integrating SemaphoreSlim with IronPDF, developers can achieve safe, efficient, and reliable concurrency control, ensuring that their applications remain responsive and performance-friendly.

Discover how IronPDF can streamline your PDF processing workflows. Try it out for yourself with its free trial starts from just $749 if you want to keep this powerful tool going in your projects.

C# Semaphoreslim (How It Works For Developers): Figure 8

< PREVIOUS
C# Init Keyword (How It Works For Developers)
NEXT >
C# try catch finally (How It Works For Developers)

Ready to get started? Version: 2024.12 just released

Free NuGet Download Total downloads: 11,622,374 View Licenses >