stride3d/stride · error · InvalidOperationException

Control is already disposed

Error message

Control is already disposed

What it means

SDLMessageLoop.Control's setter validates that the control being attached is not disposed, throwing 'Control is already disposed' otherwise. The message loop wraps a window/control to pump SDL events; attaching a dead control would leave the loop pumping events on a destroyed window.

Solutions

  1. Create a fresh window/control before assigning it to SDLMessageLoop.Control instead of reusing a disposed one.
  2. Ensure disposal order: dispose the message loop before disposing the control, and don't reassign a disposed control afterwards.
  3. Guard reattachment code: only set Control when control.IsDisposed is false; otherwise recreate.

Example fix

// before
messageLoop.Control = oldWindow; // oldWindow.IsDisposed == true
// after
if (oldWindow.IsDisposed)
    oldWindow = new Window("Stride");
messageLoop.Control = oldWindow;
Defensive patterns

Strategy: validation

Validate before calling

if (control.IsDisposed)
    control = new Window("Stride"); // recreate before attaching
messageLoop.Control = control;

Type guard

bool Attachable(Control c) => !c.IsDisposed;

Try / catch

try { messageLoop.Control = control; }
catch (InvalidOperationException ex) when (ex.Message.Contains("already disposed"))
{
    control = new Window("Stride");
    messageLoop.Control = control;
}

Prevention

When it happens

Trigger: Assigning the Control property (or constructing SDLMessageLoop) with a control whose IsDisposed is true — e.g. using a window that was already closed/disposed, or re-using a GameWindow after teardown.

Common situations: Restarting a game loop with a stale window reference after the previous run was disposed, double-disposal in custom host code, or reattaching controls across game restarts.

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 stride3d/stride@96fad776d2 (2026-09-14). Data as JSON: /api/errors/f7bfaf314ebf9436. Report an issue: GitHub.

Appendix: source

Thrown at sources/engine/Stride.Games/SDL/SDLMessageLoop.cs:69

        {
            get
            {
                return control;
            }
            set
            {
                if (control == value) return;

                // Remove any previous control
                if (control != null && !switchControl)
                {
                    isControlAlive = false;
                    control.Disposed -= ControlDisposed;
                }

                if (value != null && value.IsDisposed)
                {
                    throw new InvalidOperationException("Control is already disposed");
                }

                control = value;
                switchControl = true;
            }
        }

        /// <summary>
        /// Calls this method on each frame.
        /// </summary>
        /// <returns><c>true</c> if if the control is still active, <c>false</c> otherwise.</returns>
        /// <exception cref="System.InvalidOperationException">An error occurred </exception>
        public bool NextFrame()
        {
            // Setup new control
            // TODO this is not completely thread-safe. We should use a lock to handle this correctly
            if (switchControl && control != null)
            {

View on GitHub (pinned to 96fad776d2)