Table of Contents

Determinism and thread safety

Same input, same bytes

Convert the same document with the same options, and DocWright produces byte-identical output, on every framework (.NET Framework 4.8, .NET 8, .NET 10), operating system, processor architecture and machine culture.

That makes output easy to cache and test: hash a PDF in a unit test and the hash stays stable. It holds because:

  • Layout uses whole numbers. Every measurement is an integer number of twips, rounded once where it enters. Floating-point arithmetic, which can differ subtly between platforms, stays out of layout.
  • Nothing is ambient. Parsing and formatting use the invariant culture unless you pass one. No timestamps, GUIDs or random values are written unless you supply them: a PDF has no creation date unless you set Metadata.CreationDate, and DATE fields keep their cached value unless you give a Clock.
  • Order is stable. Nothing depends on hash-table ordering.

The one deliberate exception is encryption: salts and keys are random, as they must be.

Note

Determinism assumes the same fonts. If a font is installed on one machine and not another, the substitute can change line breaks. Ship the fonts your documents need with your application, or in your container image.

Thread safety

Object Safe to share between threads?
DocWrightConverter Yes. It holds no per-conversion state. Create one, keep it for the life of the application (a singleton in ASP.NET Core), and use it from every thread.
ConvertOptions, SaveOptions Read once at the start of each call. Use a fresh object per call, or never change a shared one.
WordDocument No. One document, one thread at a time. Different documents on different threads are fine.
PdfDocument, PdfPageAssembly No. One instance per thread. ConvertPdfToImages is safe, because it opens its own.
ReportExpression (parsed) Yes. It is immutable.
FieldHandlerRegistry Yes.
IConversionDiagnostics sinks Give each conversion its own.

Why one converter matters

The converter caches its font engine, which takes about a second to build because it indexes every installed font. A converter per request pays that second on every request. One shared converter pays it once.