{"record":{"id":"680c265721f761e1","repo":"stride3d/stride","slug":"nextframe-cannot-be-called-out-of-the-micro-thread-context","errorCode":null,"errorMessage":"NextFrame cannot be called out of the micro-thread context.","messagePattern":"NextFrame cannot be called out of the micro-thread context\\.","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"sources/core/Stride.Core.MicroThreading/Scheduler.cs","lineNumber":108,"sourceCode":"\n    /// <summary>\n    /// Yields execution.\n    /// If any other micro thread is pending, it will be run now and current micro thread will be scheduled as last.\n    /// </summary>\n    /// <returns>Task that will resume later during same frame.</returns>\n    public static MicroThreadYieldAwaiter Yield()\n    {\n        return new MicroThreadYieldAwaiter(CurrentMicroThread);\n    }\n\n    /// <summary>\n    /// Yields execution until next frame.\n    /// </summary>\n    /// <returns>Task that will resume next frame.</returns>\n    public ChannelMicroThreadAwaiter<int> NextFrame()\n    {\n        if (MicroThread.Current == null)\n            throw new Exception(\"NextFrame cannot be called out of the micro-thread context.\");\n\n        return FrameChannel.Receive();\n    }\n\n    /// <summary>\n    /// Runs until no runnable tasklets left.\n    /// This function is reentrant.\n    /// </summary>\n    public void Run()\n    {\n        int managedThreadId = Environment.CurrentManagedThreadId;\n\n        MicroThreadCallbackList callbacks = default;\n\n        try\n        {\n            runRecursion++;\n            if (runRecursion == 1)","sourceCodeStart":90,"sourceCodeEnd":126,"githubUrl":"https://github.com/stride3d/stride/blob/96fad776d210c221682aac1ccdf4c79dc046fc38/sources/core/Stride.Core.MicroThreading/Scheduler.cs#L90-L126","documentation":"Scheduler.NextFrame() returns an awaiter tied to the frame Channel; receiving from that channel requires a running micro-thread to suspend and resume. Called outside micro-thread context (MicroThread.Current == null), it throws because frame waits cannot be scheduled.","triggerScenarios":"Calling scheduler.NextFrame() from a normal Task, thread, UI event handler, or constructor rather than inside a micro-thread/script.","commonSituations":"Using NextFrame in unit tests without spawning a micro-thread; awaiting frames in utility classes invoked from non-script code; migration from async/await on Task to Stride micro-threads.","solutions":["Call NextFrame only inside micro-thread code (AsyncScript/SyncScript or Scheduler.Spawn)","For non-micro-thread code, use the engine's Update loop or an event/callback pattern instead of frame yielding","Wrap the frame-yielding logic in a micro-thread"],"exampleFix":"// before\npublic async Task Loop(Scheduler s) { while (true) await s.NextFrame(); } // run via Task\n// after\nawait Scheduler.Spawn(async mt => { while (true) await Scheduler.NextFrame(); });","handlingStrategy":"type-guard","validationCode":"if (Stride.Core.MicroThreading.MicroThread.Current == null) throw new NotSupportedException(\"NextFrame requires micro-thread context\");","typeGuard":"bool canNextFrame = MicroThread.Current != null;","tryCatchPattern":"try { await scheduler.NextFrame(); }\ncatch (Exception) when (MicroThread.Current == null) { /* use update loop instead */ }","preventionTips":["Call NextFrame only in scripts/micro-threads","Use game Update loop for non-micro-thread code","Wrap frame-looping logic in Scheduler.Spawn"],"tags":["csharp","microthreading","game-loop","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"}