{"record":{"id":"bc9e607511c3e7e1","repo":"stride3d/stride","slug":"already-scheduled-call-unschedule-before-running-this-method","errorCode":null,"errorMessage":"Already scheduled, call Unschedule before running this method","messagePattern":"Already scheduled, call Unschedule before running this method","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"sources/core/Stride.Core.MicroThreading/Scheduler.cs","lineNumber":368,"sourceCode":"        {\n            callbackList.Add(node);\n            if (schedulerEntry.CurrentQueue == null)\n                Schedule(schedulerEntry, priority, scheduleMode);\n        }\n    }\n\n    internal void Schedule(SchedulerEntry newEntry, long priority, ScheduleMode scheduleMode)\n    {\n        lock (bucketsLock)\n        {\n            ScheduleUnsafe(newEntry, priority, scheduleMode);\n        }\n    }\n\n    private void ScheduleUnsafe(SchedulerEntry newEntry, long priority, ScheduleMode scheduleMode)\n    {\n        if (newEntry.CurrentQueue != null)\n            throw new InvalidOperationException($\"Already scheduled, call {nameof(Unschedule)} before running this method\");\n\n        if (newEntry.PreviousQueue is { } previousQueue \n            && previousQueue.Owner == this\n            && previousQueue.Priority == priority\n            && previousQueue.InBucketPool == false)\n        {\n            if (previousQueue.Deque.Count == 0)\n                emptyBuckets.Remove(previousQueue.Priority);\n\n            newEntry.CurrentQueue = previousQueue;\n        }\n        // Edge case: this entry has never been scheduled, or its priority changed, or the priority was unused last schedule\n        else if (buckets.TryGetValue(priority, out newEntry.CurrentQueue) == false \n                 && emptyBuckets.Remove(priority, out newEntry.CurrentQueue) == false)\n        {\n            if (bucketPool.TryPop(out newEntry.CurrentQueue))\n                newEntry.CurrentQueue.InBucketPool = false;\n            else","sourceCodeStart":350,"sourceCodeEnd":386,"githubUrl":"https://github.com/stride3d/stride/blob/96fad776d210c221682aac1ccdf4c79dc046fc38/sources/core/Stride.Core.MicroThreading/Scheduler.cs#L350-L386","documentation":"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.","triggerScenarios":"Calling Schedule (-> ScheduleUnsafe) on the same entry/instance twice without Unschedule; scheduling an entity/micro-thread already queued this frame.","commonSituations":"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.","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"],"exampleFix":"// before\nscheduler.Schedule(entry, priority);\nscheduler.Schedule(entry, newPriority); // throws\n// after\nif (entry.CurrentQueue != null) scheduler.Unschedule(entry);\nscheduler.Schedule(entry, newPriority);","handlingStrategy":"validation","validationCode":"if (entry.CurrentQueue != null) scheduler.Unschedule(entry);","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always Unschedule before re-Schedule","Single scheduling code path per frame","Track scheduled state on your entity wrappers"],"tags":["csharp","microthreading","scheduler","stride"],"backgroundTag":"invalid-state-transition","analyzedSha":"96fad776d210c221682aac1ccdf4c79dc046fc38","analyzedAt":"2026-09-14T02:59:31.279Z","contentChangedAt":"2026-09-14T02:59:31.279Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}