Install an orchestrator package. Its dependency brings in a compatible
PdfiumRaster version automatically.
dotnet add package PdfiumRaster.OrchestratorCreate one long-lived PdfRenderOrchestrator for each application process. Prefer cancellable asynchronous creation
when the application has an asynchronous startup path:
using PdfiumRaster.Orchestration;
await using var orchestrator = await PdfRenderOrchestrator.CreateAsync(
cancellationToken: startupCancellationToken);CreateAsync completes only after every worker has connected and finished its handshake. Cancellation stops and
cleans up partially started workers. new PdfRenderOrchestrator() remains available for synchronous callers and also
returns only after all workers are ready. In Generic Host and ASP.NET Core applications, use the hosting integration;
it performs asynchronous startup automatically.
| Option | Default |
|---|---|
WorkerCount |
The smaller of 4 and Environment.ProcessorCount, with a minimum of 1 |
QueueCapacity |
42 waiting requests |
QueueFullMode |
PdfRenderQueueFullMode.Wait |
RequestTimeout |
null (hard request timeouts disabled) |
WorkerStartupTimeout |
15 seconds |
WorkerRestartDelays |
250 milliseconds, 1 second, then 4 seconds |
MaximumBatchPages |
256 pages |
MaximumInputBytes |
null (unlimited) |
MaximumBitmapBytes |
null (unlimited) |
MaximumOutputBytes |
null (unlimited) |
TemporaryDirectory |
null (use the operating-system temporary directory) |
LoggerFactory |
NullLoggerFactory.Instance (structured logging disabled) |
Pass PdfRenderOrchestratorOptions to override only the values the application needs. For example, this is a custom
configuration rather than a representation of the defaults:
using PdfiumRaster.Orchestration;
await using var orchestrator = await PdfRenderOrchestrator.CreateAsync(new PdfRenderOrchestratorOptions
{
WorkerCount = Math.Min(Environment.ProcessorCount, 4),
QueueCapacity = 100,
QueueFullMode = PdfRenderQueueFullMode.Wait,
RequestTimeout = TimeSpan.FromSeconds(30),
WorkerStartupTimeout = TimeSpan.FromSeconds(15),
MaximumBatchPages = 256,
MaximumInputBytes = 512L * 1024 * 1024,
MaximumBitmapBytes = 256L * 1024 * 1024,
MaximumOutputBytes = 512L * 1024 * 1024,
TemporaryDirectory = Path.Combine(Path.GetTempPath(), "my-app-pdf-workers"),
});WorkerCount is the number of independent PDFium processes and may not exceed the logical processor count.
QueueCapacity bounds accepted work waiting for a worker. Wait asynchronously applies backpressure; Reject throws
PdfRenderQueueFullException. RequestTimeout starts when a request is dispatched and covers input transfer, PDFium
rendering, image encoding, and output transfer while excluding queue time. A timeout promptly faults the request and
terminates that worker. A custom caller stream that ignores cancellation can delay final cleanup and orchestrator
disposal after the request task has timed out. The value must be greater than zero and no more than approximately 49.7
days; null disables the request deadline.
WorkerStartupTimeout bounds process connection and handshake time and defaults to 15 seconds.
WorkerRestartDelays defaults to 250 milliseconds, one second, and four seconds; each entry is the delay before one
replacement attempt. Both settings are validated and snapshotted during construction. See the
troubleshooting guide before increasing them in response
to persistent startup failures.
The three byte limits are optional and unlimited by default. MaximumInputBytes applies to path, array, and stream
PDF inputs; MaximumBitmapBytes applies to each uncompressed bitmap; MaximumOutputBytes applies to all bitmap pixels
or encoded output produced by one request. TemporaryDirectory selects the parent volume for private worker spool
directories. MaximumBatchPages defaults to 256 and prevents an accidentally enormous batch from monopolizing one
worker.
Use the worker-isolated inspection APIs when the application needs document metadata before choosing pages to render:
var document = await orchestrator.InspectDocumentAsync("input.pdf");
for (var pageIndex = 0; pageIndex < document.PageCount; pageIndex++)
{
var size = document.PageSizes[pageIndex];
Console.WriteLine($"Page {pageIndex}: {size.Width} x {size.Height} points");
}InspectDocumentAsync returns an immutable PdfDocumentInfo containing both PageCount and PageSizes from one
page-size inspection request. GetPageCountAsync and GetPageSizesAsync remain available when only one value is
needed. Every inspection API has path, byte[], and Stream overloads. Sizes use PDF points (1/72 inch) and the
returned list follows zero-based page order. Stream inputs are read from their current position; the orchestrator
disposes them after success, failure, cancellation, validation failure, or queue rejection unless leaveOpen: true
is passed. Byte arrays remain caller-owned and must not be modified before completion.
Inspection is scheduled through the same bounded worker pool as rendering. It observes QueueFullMode,
RequestTimeout, MaximumInputBytes, cancellation, worker replacement, and standard diagnostics. It does not render
pages, so bitmap and encoded-output limits do not apply. Path inputs remain preferable for large documents; byte and
stream inputs cross the pipe and are spooled by the worker. Failed inspections are not retried automatically.
Page indexes are zero-based. Path, byte[], and Stream PDF inputs are accepted. Prefer paths for large documents.
Byte arrays and streams must cross the named pipe and are spooled to a worker-owned, owner-only temporary directory.
Input streams are read from their current position. Unless leaveOpen: true is passed, task-returning APIs assume
ownership when the method is called and dispose the input after completion, cancellation, validation failure, or queue
rejection. RenderPagesStreamAsync is lazy: validation, submission, and stream ownership begin when enumeration
starts; an enumerable that is never enumerated does not take ownership.
using PdfiumRaster;
using PdfiumRaster.Orchestration;
var bitmap = await orchestrator.RenderPageAsync(
"input.pdf",
pageIndex: 0,
new PdfImageConversionOptions
{
Render = new PdfPageRenderOptions { Dpi = 300 },
});
await orchestrator.SavePageAsync(
"input.pdf",
pageIndex: 1,
"page-2.webp",
new PdfImageConversionOptions
{
Render = new PdfPageRenderOptions { Dpi = 144 },
Format = PdfImageOutputFormat.Webp,
});RenderPageAsync returns a caller-owned PdfBitmap. SavePageAsync can write to a path or caller-owned output stream;
output streams are never closed by the orchestrator. Rendered bitmaps and encoded images can be large; memory grows
with page dimensions, DPI, scale, and concurrent worker count. QueueCapacity bounds request count, not total retained
PDF bytes, so queued byte-array inputs can still retain substantial managed memory.
Use a batch when several pages come from the same PDF. The PDF is transferred once for array/stream inputs, opened once
with PdfRenderSession, and processed in order on one worker. RenderPagesAsync returns caller-owned bitmaps in the
same order as the requested zero-based indexes; repeated indexes are allowed. It retains all bitmap pixel arrays until
the full batch completes. SavePagesAsync accepts exact PdfPageFileOutput mappings, requires unique output paths,
and commits each file through a same-directory temporary file so an encoding or size-limit failure does not replace an
existing destination with a partial file.
var bitmaps = await orchestrator.RenderPagesAsync("input.pdf", new[] { 0, 3, 7 });
await orchestrator.SavePagesAsync("input.pdf", new[]
{
new PdfPageFileOutput(0, "page-0001.webp"),
new PdfPageFileOutput(3, "page-0004.webp"),
new PdfPageFileOutput(7, "page-0008.webp"),
}, new PdfImageConversionOptions { Format = PdfImageOutputFormat.Webp });For a large raw-bitmap batch, stream results instead of retaining the full list:
await foreach (var page in orchestrator.RenderPagesStreamAsync(
"input.pdf",
new[] { 0, 3, 7 },
cancellationToken: cancellationToken))
{
// Position identifies this item in the request, including duplicate page indexes.
await ProcessBitmapAsync(page.Position, page.PageIndex, page.Bitmap, cancellationToken);
}RenderPagesStreamAsync supports path, byte[], and Stream inputs and yields caller-owned PdfPageBitmap values in
request order. The orchestrator keeps a capacity-one channel of completed pages between its pipe reader and the
consumer. This applies consumer backpressure and avoids retaining every page, although the current page's pixel array,
one buffered result, and any bitmap still retained by the caller can coexist. Consume or release each bitmap promptly
to preserve the bounded-memory benefit.
The cancellation token controls queue admission and the entire enumeration. Ending enumeration early also aborts the request. If the request is already active, either action kills and replaces that worker because unread frames from a partially consumed batch would otherwise make its persistent pipe unsafe to reuse. A request canceled while queued is removed without replacing a worker. The request is not retried. Enumeration does not complete until any required abort and replacement attempt have finished, so a subsequent request never receives stale batch frames.
A batch occupies one worker until it finishes and pages within that batch are sequential. For a large export, split the page list into moderate batches and submit those batches together; separate workers can then run them in parallel while each batch still avoids repeated PDF transfer and parsing. If a later file in a save batch fails, files already committed by earlier items remain. The library does not retry the batch.
PdfRenderResourceLimitException reports Resource, Limit, and Observed. Known byte-array and seekable-stream
lengths can be rejected during submission; non-seekable streams and worker-generated output are rejected when the
limit is crossed. A stream output may already contain bytes at that point, so callers that require transactional
stream output should write to their own staging stream. Path outputs are staged atomically per file.
The stable Resource values are input bytes, bitmap bytes, and output bytes.
CompleteAsync() stops accepting submissions, drains accepted work, and shuts workers down. CancelAsync() cancels
queued work, waits for active uninterruptible work, and stops workers. Dispose() and DisposeAsync() follow the
cancellation path; prefer asynchronous disposal when the calling lifetime supports it. Do not create an orchestrator
per request. Graceful completion also waits for accepted streaming enumerations; consumers must keep reading, cancel,
or dispose an unfinished enumerator.
For a .NET Generic Host or ASP.NET Core application, use the built-in registration rather than manually creating a singleton and shutdown service:
builder.Services.AddPdfiumRasterOrchestrator(options =>
{
options.WorkerCount = Math.Min(Environment.ProcessorCount, 4);
options.QueueCapacity = 100;
options.RequestTimeout = TimeSpan.FromSeconds(30);
});The extension registers one PdfRenderOrchestrator singleton and one internal IHostedService. The host's
ILoggerFactory is assigned before the configuration callback runs, so an application can still replace it in the
callback. The hosted service asynchronously starts and handshakes all workers from IHostedService.StartAsync; the
host startup token cancels partial startup. Requests made after singleton resolution but before host startup finishes
are rejected. Normal shutdown calls CompleteAsync() to drain accepted work; if the host shutdown token is canceled,
the integration switches to CancelAsync() so queued work is canceled. Repeated registration calls retain the first
singleton configuration and do not duplicate the hosted service.
Add the optional readiness check and map it in ASP.NET Core:
builder.Services.AddHealthChecks()
.AddPdfiumRasterOrchestrator(tags: new[] { "ready" });
var app = builder.Build();
app.MapHealthChecks("/health/ready");The default health-check name is pdfiumraster-orchestrator; the name, exception failure status, and tags are
configurable. Its status is:
Healthywhen the orchestrator accepts requests and every configured worker is available.Degradedduring initial hosted startup or while one or more workers are unavailable or being replaced.Unhealthyafter a terminal worker failure or when completion, cancellation, or disposal starts.
The check reads in-memory lifecycle state. It does not render a document, write files, communicate over the worker
pipe, or create a separate probe worker. Inject the singleton into controllers and scoped services, but do not call
CompleteAsync() from request code. Each replica has its own singleton, so total workers equal WorkerCount
multiplied by the number of application replicas.
Call GetStatus() when an application does not use the .NET health-check integration. It returns a point-in-time
PdfRenderOrchestratorStatus containing State, AvailableWorkerCount, and WorkerCount. The state is Starting,
Healthy, Degraded, Faulted, Stopping, or Stopped. The method reads in-memory state only and never communicates
with a worker. Use the standard metrics rather than polling status for queue depth or request throughput.
PdfWorkerStartupException: a worker could not start or finish its handshake.PdfWorkerCrashedException: a worker exited during an active request; inspectExitCodeandStandardError.PdfWorkerTimeoutException: the dispatched request exceededRequestTimeout.PdfRenderResourceLimitException: configured input, bitmap, or total-output bytes were exceeded; inspectResource,Limit, andObserved.PdfWorkerRemoteException: a healthy worker reported a validation, rendering, or encoding error; inspectRemoteExceptionType.PdfWorkerProtocolException: malformed or incompatible pipe communication.PdfRenderQueueFullException: a submission was rejected because the bounded queue was full.
After a crash, timeout, or protocol failure, the orchestrator replaces the affected worker. The failed request is never retried automatically because rendering to a path or stream may have observable partial output.
Set PdfRenderOrchestratorOptions.LoggerFactory to integrate structured lifecycle logs with the application's normal
Microsoft.Extensions.Logging providers. Trace logs cover individual requests, informational logs cover orchestrator
and worker lifecycle, warnings cover timeouts, queue rejection, and worker replacement, and terminal orchestrator
faults are errors. Logging is disabled by default.
Every request also emits an internal activity through
PdfRenderOrchestratorDiagnostics.ActivitySourceName. The activity inherits the caller's Activity.Current context,
covers queueing and execution, and records operation, page count, queue duration, worker index, outcome, and error type
when applicable. Operational metrics are emitted through PdfRenderOrchestratorDiagnostics.MeterName:
The bounded operation value is render, save, render_batch, save_batch, get_page_count, or
get_page_sizes.
| Instrument | Type | Unit | Tags |
|---|---|---|---|
pdfiumraster.orchestrator.requests |
Counter | {request} |
operation, outcome |
pdfiumraster.orchestrator.request.duration |
Histogram | s |
operation, outcome |
pdfiumraster.orchestrator.queue.duration |
Histogram | s |
operation |
pdfiumraster.orchestrator.queue.size |
Observable gauge | {request} |
none |
pdfiumraster.orchestrator.requests.active |
Observable gauge | {request} |
none |
pdfiumraster.orchestrator.workers.active |
Observable gauge | {worker} |
none |
pdfiumraster.orchestrator.worker.restarts |
Counter | {attempt} |
bounded reason |
pdfiumraster.orchestrator.queue.rejections |
Counter | {request} |
none |
queue.size includes submissions waiting for queue capacity in Wait mode as well as accepted requests waiting for
an available worker. This makes admission pressure visible even when producers are being asynchronously throttled.
Configure OpenTelemetry with the public names rather than duplicating string literals:
builder.Services.AddOpenTelemetry()
.WithTracing(tracing => tracing.AddSource(
PdfRenderOrchestratorDiagnostics.ActivitySourceName))
.WithMetrics(metrics => metrics.AddMeter(
PdfRenderOrchestratorDiagnostics.MeterName));Telemetry excludes PDF and image paths, passwords, pipe names, handshake tokens, worker standard error, document
bytes, and encoded output. Request IDs and worker process IDs appear only in logs, traces, and EventSource events;
they are never metric tags. The internal EventSource provider PdfiumRaster-Orchestrator remains available for
dotnet-trace; see diagnostic events for its collection instructions and
schema.
Packaged self-contained workers support win-x86, win-x64, win-arm64, linux-arm, linux-x64, linux-arm64,
linux-musl-x64, linux-musl-arm64, osx-x64, and osx-arm64. The package does not support linux-x86 or
linux-musl-x86.
PdfiumRaster.Orchestrator is the backward-compatible all-runtime package and contains all ten workers. Applications
that deploy to several RIDs from one restored dependency graph can keep using it. A slim package named
PdfiumRaster.Orchestrator.<rid> contains the same client library, XML documentation, build target, and dependencies,
but only that RID's worker. For example:
dotnet add package PdfiumRaster.Orchestrator.linux-x64
dotnet publish -r linux-x64Choose exactly one orchestrator package. Do not reference the all-runtime and slim packages together, or multiple slim
packages together, because they contain the same client assembly and build target. When using a slim package, the
project's RuntimeIdentifier or publish -r value must match the package suffix.
A mismatched slim package and target RID fail during build or publish with an actionable error. Switch to the matching RID-specific package or to the all-runtime package when one restored dependency graph must publish for several RIDs.