stride3d/stride · error · InvalidOperationException
Already scheduled, call Unschedule before running this…
Error message
Already scheduled, call Unschedule before running this method
What it means
Scheduler.ScheduleUnsafe registers a SchedulerEntry into a priority queue; each entry may only be in one queue at a time. If newEntry.CurrentQueue is already set, it throws InvalidOperationException telling you to Unschedule first before scheduling again.
Solutions
- Call Unschedule on the entry before scheduling it again
- Track whether the entry is already scheduled (check CurrentQueue != null) before calling Schedule
- Move scheduling logic to one place so an entry is enqueued at most once per frame
Example fix
// before scheduler.Schedule(entry, priority); scheduler.Schedule(entry, newPriority); // throws // after if (entry.CurrentQueue != null) scheduler.Unschedule(entry); scheduler.Schedule(entry, newPriority);
Defensive patterns
Strategy: validation
Validate before calling
if (entry.CurrentQueue != null) scheduler.Unschedule(entry);
Prevention
- Always Unschedule before re-Schedule
- Single scheduling code path per frame
- Track scheduled state on your entity wrappers
When it happens
Trigger: Calling Schedule (-> ScheduleUnsafe) on the same entry/instance twice without Unschedule; scheduling an entity/micro-thread already queued this frame.
Common situations: Double-scheduling in an update loop (e.g. both on spawn and per-frame); re-scheduling after priority change without unscheduling; racing schedulers enqueueing the same entry.
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
- Cannot receive out of micro-thread context.
- MicroThread was already started before.
- MicroThread completed in an invalid state.
- MicroThread is already completed but still posting…
- NextFrame cannot be called out of the micro-thread context.
AI-assisted analysis of stride3d/stride@96fad776d2 (2026-09-14).
Data as JSON: /api/errors/bc9e607511c3e7e1.
Report an issue: GitHub.
Appendix: source
Thrown at sources/core/Stride.Core.MicroThreading/Scheduler.cs:368
{
callbackList.Add(node);
if (schedulerEntry.CurrentQueue == null)
Schedule(schedulerEntry, priority, scheduleMode);
}
}
internal void Schedule(SchedulerEntry newEntry, long priority, ScheduleMode scheduleMode)
{
lock (bucketsLock)
{
ScheduleUnsafe(newEntry, priority, scheduleMode);
}
}
private void ScheduleUnsafe(SchedulerEntry newEntry, long priority, ScheduleMode scheduleMode)
{
if (newEntry.CurrentQueue != null)
throw new InvalidOperationException($"Already scheduled, call {nameof(Unschedule)} before running this method");
if (newEntry.PreviousQueue is { } previousQueue
&& previousQueue.Owner == this
&& previousQueue.Priority == priority
&& previousQueue.InBucketPool == false)
{
if (previousQueue.Deque.Count == 0)
emptyBuckets.Remove(previousQueue.Priority);
newEntry.CurrentQueue = previousQueue;
}
// Edge case: this entry has never been scheduled, or its priority changed, or the priority was unused last schedule
else if (buckets.TryGetValue(priority, out newEntry.CurrentQueue) == false
&& emptyBuckets.Remove(priority, out newEntry.CurrentQueue) == false)
{
if (bucketPool.TryPop(out newEntry.CurrentQueue))
newEntry.CurrentQueue.InBucketPool = false;
elseView on GitHub (pinned to 96fad776d2)