IRONSOFTWAREHOME

How to Control Parallel OCR Memory Usage in C#

Curtis Chau
Curtis Chau
Updated: August 26, 2026

Each page processed in parallel by IronOCR allocates its own native Tesseract engine instance. For large batches - hundreds of pages from a scanned PDF or a folder of images - memory consumption scales linearly with the number of concurrent pages. The IronTesseract.MaxDegreeOfParallelism property caps how many pages are OCR'd at the same time within a single Read call, and IronTesseract.MultiThreaded switches to fully sequential processing when set to false.

Quickstart: Cap Parallel OCR with MaxDegreeOfParallelism

Set a single property to bound how many pages IronOCR reads at once, keeping peak memory inside a known budget.

  1. 1Install IronOCR with NuGet Package Manager

    PM > Install-Package IronOcr

  2. 2Copy and run this code snippet.

    var ocr = new IronOcr.IronTesseract { MaxDegreeOfParallelism = 4 };
    C#
  3. 3Deploy to test on your live environment

    Start using IronOCR in your project today with a free trial
    arrow pointer

NuGetInstall with NuGet

PM > Install-Package IronOcr

Install IronOCR by running the command above in the NuGet Package Manager Console, or search for the package in the NuGet Package Manager.

Why Does OCR Memory Grow During Large Batches?

When MultiThreaded is true, which is the default, IronOCR processes pages in parallel. Each concurrent page spins up its own native Tesseract engine holding image buffers, recognition models, and intermediate state in unmanaged memory.

Peak memory for a Read call therefore has two parts, and only one of them is under your control:

  • A fixed cost for the document itself, which grows with the number of pages regardless of how they are scheduled.
  • A marginal cost for each page read concurrently, one native engine apiece.

MaxDegreeOfParallelism acts on the second term only. That distinction matters: on a long document the fixed cost can be the larger share, so capping parallelism reduces peak memory substantially without ever driving it to zero.

In containerized environments - Docker with fixed --memory limits, Kubernetes pods with resource quotas - the marginal term is what turns a working job into an OOM kill, because it scales with the host's logical processor count rather than with anything you configured. On bare-metal servers it causes swapping and degrades throughput. We cap parallelism to keep the peak working set within a known budget.

How Much Memory Does Each Concurrent Page Use?

Measured against the reference workload below, those two terms come out as:

peak ≈ fixed document cost + (per-engine cost × concurrency)

Take the measured figures as a starting point and measure your own workload to refine them - both terms move with DPI, page complexity, document length, and language pack.

How Was This Measured?

  • Each configuration ran in its own process with the EnglishFast language pack, after a single-page warm-up, five times over.
  • Memory is the median of those runs; throughput is the fastest.

Separate processes matter because peak memory is a high-water mark that never resets - a second configuration measured in the same process would re-report the first one's peak. Throughput uses the fastest run rather than the median because other work on the machine can only ever slow a read down, never speed it up.

The corpus is "Experiences in Biodiversity Research: A Field Course" by Thea B. Gessler, Iowa State University - a 49-page text-dense PDF, downloadable so results can be reproduced. The same runs also confirmed every row of the precedence table further down this page.

What Were the Results?

MaxDegreeOfParallelismPeak memoryMarginal cost per added pageThroughput
Sequential - MultiThreaded = false782 MBbaseline0.77 pages/s
2859 MB+77 MB1.32 pages/s
41008 MB+75 MB2.09 pages/s
81279 MB+68 MB2.56 pages/s
16 - the default on this machine1829 MB+69 MB3.01 pages/s

Tested on: 8-core / 16-thread x64 laptop CPU · 1.9 GHz · 16 GB RAM · Windows 11 x64 · .NET 8 · IronOCR 2026.8.1 · last verified August 2026

The first row turns parallelism off altogether; the last is what you get by setting nothing. Peak memory is the highest the process reached at any single moment. Marginal cost per added page is the extra memory each additional concurrent page cost over the row above it - it stays close to flat down the whole column, and that steadiness is what makes the model worth planning against.

Please note: Run-to-run variation was under 2% on every configuration. Peak working set is recorded per process, so the memory column is unaffected by other software running at the time; throughput is not, so treat it as specific to this hardware.

Where Do Diminishing Returns Start?

Memory grows steadily with concurrency. Throughput does not - the gains flatten out while the memory cost keeps climbing at the same rate. Compare the last two rows: the memory column rises far more sharply between them than the throughput column does.

That is the practical finding: the default is rarely the best trade for batch work. It is tuned for latency on a single document, not for footprint. If you are processing documents in bulk on a memory-constrained host, an explicit cap somewhere below the logical processor count will usually cost you very little speed and save a great deal of memory. Measure where the knee falls on your own documents, because it moves with page complexity.

How Do I Cap How Many Pages Are Read at Once?

Set IronTesseract.MaxDegreeOfParallelism to the maximum number of pages that should process concurrently within a single Read call. Left alone, it defaults to Environment.ProcessorCount.

using IronOcr;
using System;

var ocr = new IronTesseract();

// Cap concurrency at 2 pages. Each concurrent page holds its own native
// engine, so this bounds peak memory for every Read on this instance.
ocr.MaxDegreeOfParallelism = 2;

// The cap applies per Read call, however many pages the document has.
using var input = new OcrInput();
input.LoadPdf("500-page-archive.pdf");

OcrResult result = ocr.Read(input);
Console.WriteLine(result.Text.Length);
C#

What Value Should I Choose?

Size it against the two-part model above rather than picking a number:

fixed document cost + per-engine cost × MaxDegreeOfParallelismmemory budget for OCR

To fill in the two costs for your own documents, read a representative file twice - once with MultiThreaded set to false, once with MaxDegreeOfParallelism = 2 - and record the peak working set each time. The sequential run gives you the fixed cost; the difference between the two gives you the cost of one extra engine. The reference figures for both are in the table above.

Two things worth noting when you apply it. The fixed term scales with document length, so a long archive will not behave like the sample measured above. And leave headroom for the .NET runtime, the managed heap, and anything else in the process - reserving around 40% of the container limit is a reasonable starting point.

Memory-generous hosts can leave the default in place. Constrained ones should cap explicitly, and as the diminishing-returns figures above show, that cap often costs far less throughput than its memory saving suggests.

Warning: MaxDegreeOfParallelism bounds pages within one Read call only. If your own code also runs several documents at once, the two multiply - four concurrent documents against a default of 16 is 64 engines, not 16. Cap both layers. See High Peak Memory During Bulk OCR for the outer-loop pattern.

How Do I Read Pages Sequentially?

Set MultiThreaded to false. This is the lowest-memory option, because only one native engine is ever active. The trade-off is throughput: a batch takes roughly N times longer than with N concurrent workers.

using IronOcr;

var ocr = new IronTesseract();

// One page at a time. Overrides MaxDegreeOfParallelism, whatever it is set to.
ocr.MultiThreaded = false;

using var input = new OcrInput();
input.LoadPdf("scans.pdf");

OcrResult result = ocr.Read(input);
C#
Warning: MultiThreaded had no effect before this release - it is now a live setting. Code that set it to false expecting a no-op will read sequentially after upgrading, with no compiler warning and no exception. Review existing usages if throughput drops unexpectedly.

How Do the Two Properties Interact?

MultiThreadedMaxDegreeOfParallelismEffective concurrency
true (default)Not setEnvironment.ProcessorCount
trueA positive numberThat number of concurrent pages
true0 or negativeFalls back to Environment.ProcessorCount
falseAny value1 - sequential, MaxDegreeOfParallelism is ignored
Tips: Environment.ProcessorCount reports logical processors, not physical cores. On a machine with simultaneous multithreading the default is twice the core count - an 8-core CPU defaults to 16 concurrent pages, not 8. Size your memory budget against the logical count.

Can I Bound Memory During Page Orientation Detection?

The new overload of OcrInput.DetectPageOrientation accepts a maxDegreeOfParallelism parameter that caps how many pages are orientation-detected concurrently. Non-positive values fall back to Environment.ProcessorCount. The single-argument overload is unchanged and remains source and binary compatible.

using IronOcr;
using System;

using var input = new OcrInput();
input.LoadPdf("rotated-scans.pdf");

// Analyse at most 2 pages at once. Orientation detection runs before the
// OCR read, so capping it here prevents a spike ahead of the real workload.
var results = input.DetectPageOrientation(
    OrientationDetectionMode.Fast,
    maxDegreeOfParallelism: 2);

// One result per page, in page order.
foreach (var result in results)
{
    Console.WriteLine($"Page {result.PageNumber}: {result.RotationAngle}");
}
C#

Fast mode reuses one engine per worker thread instead of allocating one per page, which reduces peak memory further. This matters when orientation detection runs as a preprocessing step before the main OCR read, since capping parallelism here prevents a memory spike before the real workload begins.

Does Parallel OCR Still Crash on Large Batches?

Previous versions could throw a fatal AccessViolationException during parallel or high-volume OCR due to unsafe cross-thread access to native Tesseract state. That exception is unrecoverable - it terminates the process without a catch opportunity. It is now resolved. The previous workarounds, one IronTesseract instance per thread and one document at a time, are no longer necessary: the engine isolates native state per worker correctly, and MaxDegreeOfParallelism provides an official cap that replaces those ad-hoc patterns.

When Should I Tune These Settings?

  • Container or CI runner with a memory cap - set MaxDegreeOfParallelism to keep peak memory within the limit.
  • Batch processing thousands of pages - lower MaxDegreeOfParallelism to prevent OOM kills. Throughput decreases proportionally, but the job completes instead of crashing.
  • Lowest possible memory - set MultiThreaded to false for sequential mode. One engine, one page at a time.
  • Maximum throughput - leave both at their defaults so the engine uses all available cores. See the multithreading for speed example.
  • Accuracy versus speed tuning - that is a different axis. The tune Tesseract for speed guide covers accuracy and speed trade-offs, while this page covers memory and throughput trade-offs.

For async and responsiveness concerns, such as keeping a UI responsive during OCR, see the async OCR how-to, which owns ReadAsync and OcrReadTask. This page covers resource bounding: keeping memory consumption predictable regardless of batch size.

Explore the IronTesseract configuration guide for the full property surface, loading PDF inputs and loading image inputs for OcrInput usage patterns, and page orientation detection for the DetectPageOrientation method and its new parallelism overload.

Explore IronOCR licensing options to move from trial to production.

Curtis Chau
Technical Writer

Curtis Chau holds a Bachelor’s degree in Computer Science (Carleton University) and specializes in front-end development with expertise in Node.js, TypeScript, JavaScript, and React. Passionate about crafting intuitive and aesthetically pleasing user interfaces, Curtis enjoys working with modern frameworks and creating well-structured, visually appealing manuals.

...
Read More

Ready to Get Started?

Nuget Downloads 6,236,385Version:2026.9just released

Get your free 30-day Trial Key instantly.
No credit card or account creation required
C# NuGet Library for PDF
Install with NuGet

Version: 2026.9

PM > Install-Package IronOcr
nuget.org/packages/IronOcr/
  1. In Solution Explorer, right-click References, Manage NuGet Packages
  2. Select Browse and search "IronOCR"
  3. Select the package and install
C# PDF DLL
Download DLL

Version: 2026.9

or download Windows Installer here.

  1. Download and unzip IronOCR to a location such as ~/Libs within your Solution directory
  2. In Visual Studio Solution Explorer, right click References. Select Browse, "IronOCR.dll"

Licenses from $999

Key in blue circle

Get your free 30-day Trial Key instantly.

Your trial license will be sent to your email address

No limitations. 100% unlocked. No credit card.

bullet_checkedNo credit card or account creation requiredNo limitations. 100% unlocked. No credit card.
  • Logo Aetna
  • Logo NASA
  • Logo GE
  • Logo Porsche
  • Logo USDA
  • Logo Qatar
Join Millions of Engineers who’ve tried IronPDF
Get Your No-Obligation Consult
Complete the form below or email sales@ironsoftware.com
Your details will always be kept confidential.
Trusted by Millions of Engineers Worldwide
Iron Software's customer logos
Get your free 30-day Trial Key instantly.
No credit card or account creation required