Click or drag to resize

Multithreading Support

The multithreading support was one of the key factors in the design of the HTML to PDF library, because most of the applications today are multithreaded applications. An ASP.NET application is a very good example of multithreaded application, because at each request a user makes for a web page the ASP.NET subsystem from IIS creates a new thread in the worker process to execute the page and to return the result to the browser. Because many of the web applications today are ASP.NET applications you can easily understand how important is for a library to perform correctly and reliably in a multithreaded environment.

The render and save methods of the PdfConverter object can be safely called from multiple threads of an application and all the memory and resources used by the converter are automatically disposed after each conversion. The library was designed and carefully tested to not leak any memory or system resources after a conversion. Because of this, the library can be safely used in services running a very long period without interruption and in highly loaded systems.

To demonstrate this, we have created the Console_MultithreadedPerformance sample that you can use to test the behavior and the performance of the converter in a multithreaded application.

ExpertPdf controls the number of threads in the current .NET application domain that can convert HTML to PDF simultaneously. Use the static property PdfConverterConcurrencyLevel for that. This parameter must be set before the first conversion performed in the current application domain. When this property is set with a negative value or zero the concurrency level is maximum. The default value is 4.

Sample Code

Console_MultithreadedPerformance sample full source code:

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Threading;

using ExpertPdf.HtmlToPdf;

namespace Console_MultithreadedPerformance
{
    class Program
    {
        const bool MULTITHREADED = true;
        const int THREADS_COUNT = 8;
        const int CONVERSIONS_PER_THREAD_COUNT = 50;

        static string urlToConvert = null;
        static int totalConversionsCount = 0;
        static DateTime profilingStartTime = DateTime.MinValue;

        static void Main(string[] args)
        {
            if (args.Length > 2)
            {
                Console.WriteLine("Usage: Console_MultithreadedPerformance.exe [URL] ");
                return;
            }

            string appPath = GetAppPath();

            if (args.Length == 1)
                urlToConvert = args[0];
            else
                urlToConvert = Path.Combine(appPath, @"..\..\HTML\htmltopdf.htm");

            PdfConverter.PdfConverterConcurrencyLevel = THREADS_COUNT;

            if (MULTITHREADED)
            {
                Console.WriteLine(String.Format("Output directory: {0}\n", Path.Combine(GetAppPath(), @"..\..\OutPDF")));
                Console.WriteLine(String.Format("Profiling started. {0} threads. {1} conversions per thread. URL: {2}.\n", 
                    THREADS_COUNT, CONVERSIONS_PER_THREAD_COUNT, urlToConvert));

                totalConversionsCount = 0;
                profilingStartTime = DateTime.Now;

                // start the worker threads to convert HTML to PDF
                Thread[] workerThreads = new Thread[THREADS_COUNT];
                for (int threadIdx = 0; threadIdx < THREADS_COUNT; threadIdx++)
                {
                    workerThreads[threadIdx] = new Thread(new ParameterizedThreadStart(ConvertHtmlToPdf));
                    workerThreads[threadIdx].Start(threadIdx);
                }

                // wait for the worker threads to end
                for (int threadIdx = 0; threadIdx < workerThreads.Length; threadIdx++)
                    workerThreads[threadIdx].Join();

                // global statistics

                DateTime profilingEndTime = DateTime.Now;

                // the total time since all conversions started
                double totalTime = profilingEndTime.Subtract(profilingStartTime).TotalSeconds;
                // the average conversion speed in PDFs per second
                double averageConversionSpeed = ((double)totalConversionsCount) / totalTime;

                Console.WriteLine(String.Format("\nProfiling ended. {0} conversions in {1:F2} sec. Avg speed: {2:F2} pdf/sec.",
                    totalConversionsCount, totalTime, averageConversionSpeed));
                Console.WriteLine("Press ENTER to exit.");

                Console.ReadLine();
            }
            else
            {
                Console.WriteLine(String.Format("Output directory: {0}\n", Path.Combine(GetAppPath(), @"..\..\OutPDF")));
                Console.WriteLine(String.Format("Profiling started. Not multithreaded. URL: {0}.\n", urlToConvert));

                totalConversionsCount = 0;
                profilingStartTime = DateTime.Now;
                double totalTime = 0;
                double averageConversionSpeed = 0;

                for (int convIdx = 0; convIdx < THREADS_COUNT * CONVERSIONS_PER_THREAD_COUNT; convIdx++)
                {
                    try
                    {
                        // create the HTML to PDF converter
                        PdfConverter pdfConverter = new PdfConverter();
                        // get the output PDF file path
                        string outPdfFile = Path.Combine(Path.Combine(GetAppPath(), @"..\..\OutPDF"), String.Format("RenderedPage_{0}.pdf", convIdx));

                        DateTime startTime = DateTime.Now;

                        // do the HTML to PDF conversion
                        pdfConverter.SavePdfFromUrlToFile(urlToConvert, outPdfFile);

                        DateTime endTime = DateTime.Now;

                        // the time in seconds taken by this conversion
                        double thisConversionTime = endTime.Subtract(startTime).TotalSeconds;

                        // the total number of conversions
                        totalConversionsCount++;
                        // the total time since all conversions started
                        totalTime = endTime.Subtract(profilingStartTime).TotalSeconds;
                        // the average conversion speed in PDFs per second
                        averageConversionSpeed = ((double)totalConversionsCount) / totalTime;

                        Console.WriteLine(String.Format("Iteration {0}: {1:F2} sec. Avg speed: {2:F2} pdf/sec", convIdx, 
                            thisConversionTime, averageConversionSpeed));
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine(ex.Message);
                    }
                }

                // global statistics

                DateTime profilingEndTime = DateTime.Now;

                // the total time since all conversions started
                totalTime = profilingEndTime.Subtract(profilingStartTime).TotalSeconds;
                // the average conversion speed in PDFs per second
                averageConversionSpeed = ((double)totalConversionsCount) / totalTime;

                Console.WriteLine(String.Format("\nProfiling ended. {0} conversions in {1:F2} sec. Avg speed: {2:F2} pdf/sec.",
                    totalConversionsCount, totalTime, averageConversionSpeed));
                Console.WriteLine("Press ENTER to exit.");

                Console.ReadLine();
            }
        }

        private static void ConvertHtmlToPdf(object threadIdxObj)
        {
            int threadIdx = (int)threadIdxObj;


            for (int convIdx = 0; convIdx < CONVERSIONS_PER_THREAD_COUNT; convIdx++)
            {
                try
                {
                    // create the HTML to PDF converter
                    PdfConverter pdfConverter = new PdfConverter();
                    // get the output PDF file path
                    string outPdfFile = Path.Combine(Path.Combine(GetAppPath(), @"..\..\OutPDF"), String.Format("RenderedPage_{0}_{1}.pdf", threadIdx, convIdx));

                    DateTime startTime = DateTime.Now;

                    // do the HTML to PDF conversion
                    pdfConverter.SavePdfFromUrlToFile(urlToConvert, outPdfFile);

                    DateTime endTime = DateTime.Now;

                    // the time in seconds taken by this conversion
                    double thisConversionTime = endTime.Subtract(startTime).TotalSeconds;

                    // the total number of conversions
                    int totalConversions = Interlocked.Increment(ref totalConversionsCount);
                    // the total time since all conversions started
                    double totalTime = endTime.Subtract(profilingStartTime).TotalSeconds;
                    // the average conversion speed in PDFs per second
                    double averageConversionSpeed = ((double)totalConversions)/totalTime;

                    Console.WriteLine(String.Format("Thread {0} iteration {1}: {2:F2} sec. Conversions: {3}. Avg speed: {4:F2} pdf/sec", threadIdx, convIdx,
                        thisConversionTime, totalConversions, averageConversionSpeed));
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                }
            }

        }

        private static string GetAppPath()
        {
            return Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
        }
    }
}