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, andDATEfields keep their cached value unless you give aClock. - 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.