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.
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); } } }