stride3d/stride · error · InvalidOperationException
Control is already disposed
Error message
Control is already disposed
What it means
The WindowsMessageLoop.Control setter validates that the control being attached as the message-loop target is not already disposed; assigning a disposed Control throws InvalidOperationException because the loop cannot pump messages from a destroyed window handle.
Solutions
- Check control.IsDisposed before assigning it to the message loop
- Create a fresh Form/Control instance if the old one was disposed
- Synchronize with the UI thread (Invoke/BeginInvoke) so the loop isn't started with a mid-disposal control
Example fix
// before
loop.Control = cachedForm; // may already be disposed
// after
if (cachedForm == null || cachedForm.IsDisposed)
cachedForm = new RenderForm(...);
loop.Control = cachedForm; Defensive patterns
Strategy: validation
Validate before calling
if (control == null || control.IsDisposed)
throw new InvalidOperationException("Cannot attach a disposed or null control to the message loop");
loop.Control = control; Type guard
bool IsUsableControl(Control c) => c is { IsDisposed: false }; Try / catch
try { loop.Control = control; } catch (InvalidOperationException) { control = new RenderForm(...); loop.Control = control; } Prevention
- Check IsDisposed before reusing cached form references
- Marshal loop startup onto the UI thread to avoid races with form disposal
When it happens
Trigger: Setting the loop's Control property (or constructing WindowsMessageLoop/Run) with a Form/Control whose IsDisposed is true — e.g. after the window was closed.
Common situations: Restarting a render loop after the form closed; holding a form reference past disposal; racing a background thread that starts the loop while the UI thread closes the window.
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
- form
- renderCallback
- Cannot enable tracking when this instance is disposed
- Control is already disposed
- uiControl
AI-assisted analysis of stride3d/stride@96fad776d2 (2026-09-14).
Data as JSON: /api/errors/0cf9cb71bd205b60.
Report an issue: GitHub.
Appendix: source
Thrown at sources/engine/Stride.Games/Desktop/WindowsMessageLoop.cs:98
get
{
return control;
}
set
{
if (control == value) return;
// Remove any previous control
if (control != null && !switchControl)
{
isControlAlive = false;
control.Disposed -= ControlDisposed;
controlHandle = IntPtr.Zero;
}
if (value != null && value.IsDisposed)
{
throw new InvalidOperationException("Control is already disposed");
}
control = value;
switchControl = true;
}
}
/// <summary>
/// Gets or sets a value indicating whether the render loop should use the default <see cref="Application.DoEvents"/> instead of a custom window message loop lightweight for GC. Default is false.
/// </summary>
/// <value><c>true</c> if the render loop should use the default <see cref="Application.DoEvents"/> instead of a custom window message loop (default false); otherwise, <c>false</c>.</value>
/// <remarks>By default, RenderLoop is using a custom window message loop that is more lightweight than <see cref="Application.DoEvents" /> to process windows event message.
/// Set this parameter to true to use the default <see cref="Application.DoEvents"/>.</remarks>
public bool UseApplicationDoEvents { get; set; }
/// <summary>
/// Calls this method on each frame.
/// </summary>View on GitHub (pinned to 96fad776d2)