dotnet/wpf · error · InvalidOperationException
SR.MeasureReentrancyInvalid
Error message
SR.MeasureReentrancyInvalid
What it means
TextBlock throws this InvalidOperationException when a public API or text-view callback is invoked while the layout Measure pass is still running. WPF requires layout passes to complete atomically; mutating content or querying layout during measure would corrupt cached line metrics. VerifyReentrancy() at the start of every public method guards against this.
Solutions
- Move the mutating call out of the measure path: defer it with Dispatcher.BeginInvoke(DispatcherPriority.Loaded/Background) or post it after the current layout pass completes.
- If inside MeasureOverride of a derived class, compute desired size without modifying the TextBlock's content; do content changes before calling base.MeasureOverride or in response to user input instead.
- Cache or batch property updates and apply them once after layout (e.g. on CompositionTarget.Rendering or via BeginInvoke) rather than per-measure-event.
- If triggered by an automation peer or textview callback, defer the query until the layout pass finishes instead of resolving it inline.
Example fix
// before: modifying text during layout
protected override Size MeasureOverride(Size constraint)
{
if (needsUpdate) { textBlock.Text = ComputeText(); } // throws MeasureReentrancyInvalid
return base.MeasureOverride(constraint);
}
// after: defer the mutation until the layout pass is over
protected override Size MeasureOverride(Size constraint)
{
if (needsUpdate)
{
Dispatcher.BeginInvoke(DispatcherPriority.Loaded, new Action(() => textBlock.Text = ComputeText()));
needsUpdate = false;
}
return base.MeasureOverride(constraint);
} Defensive patterns
Strategy: try-catch
Validate before calling
bool safeToTouchTextBlock = !textBlock.IsMeasureValid || !Dispatcher.HasStarted || Dispatcher.CheckAccess(); // schedule work only when not inside a layout pass: if (!safeToTouchTextBlock) Dispatcher.BeginInvoke(DispatcherPriority.Loaded, new Action(UpdateText));
Type guard
static bool CanModifyNow(TextBlock tb) => tb.Dispatcher.CheckAccess() && !DesignerProperties.GetIsInDesignMode(tb) && tb.IsLoaded == false || tb.IsMeasureValid && tb.IsArrangeValid;
Try / catch
try
{
textBlock.Text = newText;
}
catch (InvalidOperationException ex) when (ex.Message.Contains("measure"))
{
Dispatcher.BeginInvoke(DispatcherPriority.Loaded, new Action(() => textBlock.Text = newText));
} Prevention
- Never mutate TextBlock.Text/Inlines from MeasureOverride, ArrangeOverride, LayoutUpdated, or SizeChanged handlers.
- Defer programmatic text changes with Dispatcher.BeginInvoke to a priority below Render.
- In derived controls, treat the measure/arrange pass as read-only.
- Batch automation-driven queries instead of resolving them inline during layout.
When it happens
Trigger: Calling any public TextBlock member (e.g. setting Text/Inlines, calling ContentStart/ContentEnd text positions, or textview methods like GetLineText/GetLineIndexFromCharacterIndex) from within MeasureOverride, from a layout-time event, or from code invoked synchronously during the measure pass of the TextBlock.
Common situations: Subscribing to events that fire during layout (e.g. ScrollChanged, LayoutUpdated) and touching the TextBlock; custom controls deriving from TextBlock and modifying Inlines inside MeasureOverride; automation/UIA peers querying text properties during measure.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- SR.ArrangeReentrancyInvalid
- SR.ArrangeReentrancyInvalid
- SR.FlowDocumentInvalidContnetChange
- SR.Format(SR.LayoutManager_DeepRecursion…
- SR.IllegalTreeChangeDetected
AI-assisted analysis of dotnet/wpf@81131a70a4 (2026-09-14).
Data as JSON: /api/errors/287eb0708de628d4.
Report an issue: GitHub.
Appendix: source
Thrown at src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/TextBlock.cs:3559
_flags = value ? (_flags | flags) : (_flags & (~flags));
}
// ------------------------------------------------------------------
// CheckFlags returns true if all of passed flags in the bitmask are set.
// ------------------------------------------------------------------
private bool CheckFlags(Flags flags)
{
return ((_flags & flags) == flags);
}
// ------------------------------------------------------------------
// Ensures none of our public (or textview) methods can be called during measure/arrange/content change.
// ------------------------------------------------------------------
private void VerifyReentrancy()
{
if(CheckFlags(Flags.MeasureInProgress))
{
throw new InvalidOperationException(SR.MeasureReentrancyInvalid);
}
if(CheckFlags(Flags.ArrangeInProgress))
{
throw new InvalidOperationException(SR.ArrangeReentrancyInvalid);
}
if(CheckFlags(Flags.ContentChangeInProgress))
{
throw new InvalidOperationException(SR.TextContainerChangingReentrancyInvalid);
}
}
/// <summary>
/// Returns index of the line that starts at the given dcp. Returns -1 if
/// no line or the line metrics collection starts at the given dcp
/// </summary>
/// <param name="dcpLine">View on GitHub (pinned to 81131a70a4)