dotnet/wpf · error · InvalidOperationException

SR.FlowDocumentFormattingReentrancy

Error message

SR.FlowDocumentFormattingReentrancy

What it means

FlowDocumentPaginator.GetPageAsync throws InvalidOperationException(SR.FlowDocumentFormattingReentrancy) when GetPageAsync is called while formatting is already in progress on the FlowDocument (StructuralCache.IsFormattingInProgress). Pagination drives formatting internally; a nested request would corrupt the incremental formatting state, so WPF throws.

Solutions

  1. Do not call GetPageAsync from pagination/formatting callbacks; queue the request and issue it after the current operation completes (e.g. via Dispatcher.BeginInvoke).
  2. Use the paginator's async completion events to trigger subsequent page requests only after handlers return.
  3. Avoid calling GetPage (synchronous) from code paths that can run while an async GetPageAsync is mid-flight on the same dispatcher.
  4. Share one paginator per FlowDocument and serialize access to GetPageAsync.

Example fix

// before
void OnPaginationProgress(object sender, PaginationProgressEventArgs e) {
    paginator.GetPageAsync(e.Start + e.Count, null); // inline during formatting
}
// after
void OnPaginationProgress(object sender, PaginationProgressEventArgs e) {
    Dispatcher.BeginInvoke(new Action(() =>
        paginator.GetPageAsync(e.Start + e.Count, null)),
        System.Windows.Threading.DispatcherPriority.Background);
}
Defensive patterns

Strategy: try-catch

Validate before calling

if (!paginator.Document.StructuralCache.IsFormattingInProgress &&
    !paginator.Document.StructuralCache.IsContentChangeInProgress)
    paginator.GetPageAsync(pageNumber, userState);

Type guard

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

Try / catch

try { paginator.GetPageAsync(n, state); }
catch (InvalidOperationException) {
    Dispatcher.BeginInvoke(new Action(() => paginator.GetPageAsync(n, state)), DispatcherPriority.Background);
}

Prevention

When it happens

Trigger: Calling GetPageAsync from within a callback that executes during formatting — e.g. inside GetPage's synchronous work, inside PaginationProgress/PaginationCompleted handlers when they run inline, or from a thread re-entering the paginator while a prior GetPageAsync is executing its formatting phase.

Common situations: Chained pagination event handlers that immediately request more pages; custom paginators that call base GetPageAsync inside OnGetPage; UI code that forces pagination synchronously from a layout event.

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/623e8cd4bf87009c. Report an issue: GitHub.

Appendix: source

Thrown at src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/documents/FlowDocumentPaginator.cs:79

        /// Async version of <see cref="DocumentPaginator.GetPage"/>
        /// </summary>
        /// <param name="pageNumber">Page number.</param>
        /// <param name="userState">Unique identifier for the asynchronous task.</param>
        /// <exception cref="ArgumentOutOfRangeException">
        /// Throws ArgumentOutOfRangeException if PageNumber is negative.
        /// </exception>
        public override void GetPageAsync(int pageNumber, object userState)
        {
            // Page number cannot be negative.
            if (pageNumber < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(pageNumber), SR.IDPNegativePageNumber);
            }

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

            DocumentPage page = null;

            if (!_backgroundPagination)
            {
                page = GetPage(pageNumber);
            }
            else
            {
                // If entire content has been already pre-paginated (BreakRecordTable is clean)
                // and requesting non-existing page number, return DocumentPage.Missing.
                if (_brt.IsClean && !_brt.HasPageBreakRecord(pageNumber))
                {

View on GitHub (pinned to 81131a70a4)