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

  1. Wrap the update in using (wb.Lock()) { ... wb.AddDirtyRect(rect); }
  2. Check that Lock/TryLock succeeded (TryLock returns false when another thread holds the lock) before calling AddDirtyRect
  3. Ensure Unlock is not called before all AddDirtyRect calls complete
  4. 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

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


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)