dotnet/wpf · error · InvalidOperationException
SR.Image_MustBeLocked
Error message
SR.Image_MustBeLocked
What it means
AddDirtyRect may only be called between TryLock/BeginInit lock acquisition and Unlock. WritePreamble plus the _lockCount == 0 check throw InvalidOperationException with SR.Image_MustBeLocked when the bitmap is not currently locked.
Solutions
- Wrap the update in using (wb.Lock()) { ... wb.AddDirtyRect(rect); }
- Check that Lock/TryLock succeeded (TryLock returns false when another thread holds the lock) before calling AddDirtyRect
- Ensure Unlock is not called before all AddDirtyRect calls complete
- Perform lock/AddDirtyRect/Unlock on the same thread that owns the lock
Example fix
// before
wb.AddDirtyRect(new Int32Rect(0, 0, 10, 10)); // throws: not locked
// after
using (wb.Lock())
{
// write pixels...
wb.AddDirtyRect(new Int32Rect(0, 0, 10, 10));
} Defensive patterns
Strategy: try-catch
Validate before calling
// call only inside a lock scope
using (wb.Lock())
{
wb.AddDirtyRect(rect);
} Type guard
// no type guard; check lock state by structuring code inside Lock scope
static void SafeAddDirtyRect(WriteableBitmap wb, Int32Rect r)
{
using (wb.Lock()) { wb.AddDirtyRect(r); }
} Try / catch
try { wb.AddDirtyRect(rect); }
catch (InvalidOperationException ex) when (ex.Message.Contains("lock"))
{
using (wb.Lock()) { wb.AddDirtyRect(rect); }
} Prevention
- Always pair Lock/Unlock (or use the Lock() disposable) around AddDirtyRect
- Never call AddDirtyRect after Unlock; batch all rect updates in one lock scope
- With TryLock, check the bool return before proceeding
- Keep bitmap updates on the thread that owns the lock
When it happens
Trigger: Calling AddDirtyRect before Lock/TryLock (or after Unlock), or on a bitmap created via the constructor without ever locking.
Common situations: Updating pixels outside the using/Lock scope; forgetting Lock after re-creating the bitmap; calling AddDirtyRect from another thread that didn't lock.
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.Image_IndexedPixelFormatRequiresPalette
- ArgumentNullException (buffer/sourceBuffer was IntPtr.Zero)
- ArgumentOutOfRangeException (timeout was Duration.Automatic)
- Cannot countersign an unsigned package.
- Cannot read Page properties because it is not in a tree…
AI-assisted analysis of dotnet/wpf@81131a70a4 (2026-09-14).
Data as JSON: /api/errors/df10ba5f364365ef.
Report an issue: GitHub.
Appendix: source
Thrown at src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Media/Imaging/WriteableBitmap.cs:160
/// </summary>
/// <param name="dirtyRect">
/// An Int32Rect structure specifying the dirty region.
/// </param>
/// <remarks>
/// This method can be called multiple times, and the areas are accumulated
/// in a sufficient, but not necessarily minimal, representation. For efficiency,
/// only the areas that are marked as dirty are guaranteed to be copied over to
/// the rendering system.
/// AddDirtyRect can only be called while the bitmap is locked, otherwise an
/// InvalidOperationException will be thrown.
/// </remarks>
public void AddDirtyRect(Int32Rect dirtyRect)
{
WritePreamble();
if (_lockCount == 0)
{
throw new InvalidOperationException(SR.Image_MustBeLocked);
}
//
// Sanitize the dirty rect.
//
dirtyRect.ValidateForDirtyRect(nameof(dirtyRect), _pixelWidth, _pixelHeight);
if (dirtyRect.HasArea)
{
MILSwDoubleBufferedBitmap.AddDirtyRect(
_pDoubleBufferedBitmap,
ref dirtyRect);
_hasDirtyRects = true;
}
// Note: we do not call WritePostscript because we do not want to
// raise change notifications until the writeable bitmap is unlocked.
}View on GitHub (pinned to 81131a70a4)