dotnet/wpf · error · InvalidOperationException

SR.FlowDocumentFormattingReentrancy

Error message

SR.FlowDocumentFormattingReentrancy

What it means

FlowDocumentFormatter.Format throws InvalidOperationException(SR.FlowDocumentFormattingReentrancy) when formatting is requested while another formatting pass is already in progress on the same FlowDocument (StructuralCache.IsFormattingInProgress is true). WPF's FlowDocument formatting state is not reentrant; nested calls would corrupt incremental layout state. The library fails fast instead of queueing the work.

Solutions

  1. Ensure the code path that re-triggers formatting is not invoked during an active formatting pass; defer it (Dispatcher.BeginInvoke) instead of calling synchronously.
  2. Never call Format/GetPage/GetPageAsync from within pagination callbacks; capture the request and process it after the current operation completes.
  3. If custom code mutates the document, batch changes and let layout complete before requesting new pages.
  4. Use the same DocumentPaginator instance rather than starting a second formatting pipeline concurrently.

Example fix

// before
void OnPaginationCompleted(object sender, EventArgs e) {
    paginator.GetPageAsync(0, null); // may still be inside formatting
}
// after
void OnPaginationCompleted(object sender, EventArgs e) {
    Dispatcher.BeginInvoke(new Action(() => paginator.GetPageAsync(0, null)),
        System.Windows.Threading.DispatcherPriority.Background);
}
Defensive patterns

Strategy: validation

Validate before calling

if (_document.StructuralCache.IsFormattingInProgress)
    return; // or defer; do not call Format now

Type guard

bool CanFormat(FlowDocument doc) => !doc.StructuralCache.IsFormattingInProgress && !doc.StructuralCache.IsContentChangeInProgress;

Try / catch

try { formatter.Format(offset); }
catch (InvalidOperationException ex) when (ex.Message.Contains("reentrancy") || ex.Message.Contains("formatting")) {
    Dispatcher.BeginInvoke(new Action(() => formatter.Format(offset)), DispatcherPriority.Background);
}

Prevention

When it happens

Trigger: Calling FlowDocumentFormatter.Format (directly or via layout/pagination) from inside code that runs while formatting is in progress — e.g. inside a GetPage/GetPageAsync callback, a DocumentPaginator event handler, or a property-changed handler that synchronously re-triggers Format.

Common situations: Custom DocumentPaginator subclasses that call back into document APIs during OnGetPage; event handlers that modify layout-triggering properties while pagination is running; synchronously forcing layout from within a Print/print-chain callback.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


AI-assisted analysis of dotnet/wpf@81131a70a4 (2026-09-14). Data as JSON: /api/errors/446d31866c437c87. Report an issue: GitHub.

Appendix: source

Thrown at src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/documents/FlowDocumentFormatter.cs:59

        //  Internal Methods
        //
        //-------------------------------------------------------------------

        #region Internal Methods

        /// <summary>
        /// Formatts content.
        /// </summary>
        /// <param name="constraint">Constraint size.</param>
        internal void Format(Size constraint)
        {
            Thickness pageMargin;
            Size pageSize;

            // Reentrancy check.
            if (_document.StructuralCache.IsFormattingInProgress)
            {
                throw new InvalidOperationException(SR.FlowDocumentFormattingReentrancy);
            }
            if (_document.StructuralCache.IsContentChangeInProgress)
            {
                throw new InvalidOperationException(SR.TextContainerChangingReentrancyInvalid);
            }

            // Check if we can continue with formatting without nuking incremental udpate info.
            if (_document.StructuralCache.IsFormattedOnce)
            {
                if (!_lastFormatSuccessful)
                {
                    // We cannot resolve update info if last formatting was unsuccessful.
                    _document.StructuralCache.InvalidateFormatCache(true);
                }
                if (!_arrangedAfterFormat && (!_document.StructuralCache.ForceReformat || !_document.StructuralCache.DestroyStructure))
                {
                    // Need to clear update info by running arrange process.
                    // This is necessary, because Format may be called more than once

View on GitHub (pinned to 81131a70a4)