stride3d/stride · error · InvalidOperationException
Cannot dispose while a job is in flight
Error message
Cannot dispose while a job is in flight
What it means
This Dispose implementation refuses to release the worker buffer pools while a dispatch job is still in flight: _managedContext or _unmanagedContext is non-null, meaning a dispatch is between begin and completion. Disposing then would free memory the running job still uses, so it throws InvalidOperationException instead of corrupting state.
Solutions
- Wait for all in-flight jobs to complete (join the dispatch / end the job) before calling Dispose().
- Ensure shutdown ordering: stop the simulation loop, then dispose the dispatcher.
- Synchronize disposal with the code path that starts jobs (lock or task completion await).
Example fix
// before dispatcher.Dispose(); // while job running // after await taskUsingDispatcher; // or dispatcher.EndDispatch(...) dispatcher.Dispose();
Defensive patterns
Strategy: try-catch
Validate before calling
bool isIdle = dispatcher.HasNoInFlightJobs; // expose/check before disposing canDispose = isIdle || _disposed;
Try / catch
try { dispatcher.Dispose(); } catch (InvalidOperationException) { await inFlightTask; dispatcher.Dispose(); } Prevention
- Stop the simulation loop before tearing down the dispatcher
- Track in-flight jobs and await their completion in shutdown code
- Serialize disposal and dispatch on the same lock or owner thread
When it happens
Trigger: Calling DispatcherWrapper.Dispose() from another thread while an outstanding dispatch (Begin/End job) has not finished, or tearing down the simulation while worker threads still reference the dispatcher.
Common situations: App shutdown racing with a physics step; disposing the simulation in a scene unload while a job started on another thread is mid-execution.
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
- Thread count must be positive.
- Trying to lock while another thread owns the lock.
- Trying to unlock while another thread owns the lock.
- The object must be IDisposable, IReferenceable, or IntPtr
- A dispatcher lock must be created from a different thread…
AI-assisted analysis of stride3d/stride@96fad776d2 (2026-09-14).
Data as JSON: /api/errors/f5d6c045bea3d4a2.
Report an issue: GitHub.
Appendix: source
Thrown at sources/engine/Stride.BepuPhysics/Stride.BepuPhysics/BepuSimulation.cs:1186
_workerType = WorkerType.Managed;
_managedWorker = workerBody;
SignalThreads(maximumWorkerCount);
_managedWorker = null;
}
else if (maximumWorkerCount == 1)
{
workerBody(0);
}
_unmanagedContext = null;
_managedContext = null;
}
public void Dispose()
{
if (!_disposed)
{
if (_managedContext is not null || _unmanagedContext is not null)
throw new InvalidOperationException("Cannot dispose while a job is in flight");
_disposed = true;
WorkerPools.Dispose();
}
}
private enum WorkerType
{
Managed,
Unmanaged,
}
private readonly struct Job(DispatcherWrapper wrapper) : Dispatcher.IBatchJob
{
public void Process(int start, int endExclusive)
{
for (int i = start; i < endExclusive; i++)
wrapper.DispatchThread(i);View on GitHub (pinned to 96fad776d2)