stride3d/stride · error · Exception

NextFrame cannot be called out of the micro-thread context.

Error message

NextFrame cannot be called out of the micro-thread context.

What it means

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.

Solutions

  1. Call NextFrame only inside micro-thread code (AsyncScript/SyncScript or Scheduler.Spawn)
  2. For non-micro-thread code, use the engine's Update loop or an event/callback pattern instead of frame yielding
  3. Wrap the frame-yielding logic in a micro-thread

Example fix

// before
public async Task Loop(Scheduler s) { while (true) await s.NextFrame(); } // run via Task
// after
await Scheduler.Spawn(async mt => { while (true) await Scheduler.NextFrame(); });
Defensive patterns

Strategy: type-guard

Validate before calling

if (Stride.Core.MicroThreading.MicroThread.Current == null) throw new NotSupportedException("NextFrame requires micro-thread context");

Type guard

bool canNextFrame = MicroThread.Current != null;

Try / catch

try { await scheduler.NextFrame(); }
catch (Exception) when (MicroThread.Current == null) { /* use update loop instead */ }

Prevention

When it happens

Trigger: Calling scheduler.NextFrame() from a normal Task, thread, UI event handler, or constructor rather than inside a micro-thread/script.

Common situations: 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.

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


AI-assisted analysis of stride3d/stride@96fad776d2 (2026-09-14). Data as JSON: /api/errors/680c265721f761e1. Report an issue: GitHub.

Appendix: source

Thrown at sources/core/Stride.Core.MicroThreading/Scheduler.cs:108

    /// <summary>
    /// Yields execution.
    /// If any other micro thread is pending, it will be run now and current micro thread will be scheduled as last.
    /// </summary>
    /// <returns>Task that will resume later during same frame.</returns>
    public static MicroThreadYieldAwaiter Yield()
    {
        return new MicroThreadYieldAwaiter(CurrentMicroThread);
    }

    /// <summary>
    /// Yields execution until next frame.
    /// </summary>
    /// <returns>Task that will resume next frame.</returns>
    public ChannelMicroThreadAwaiter<int> NextFrame()
    {
        if (MicroThread.Current == null)
            throw new Exception("NextFrame cannot be called out of the micro-thread context.");

        return FrameChannel.Receive();
    }

    /// <summary>
    /// Runs until no runnable tasklets left.
    /// This function is reentrant.
    /// </summary>
    public void Run()
    {
        int managedThreadId = Environment.CurrentManagedThreadId;

        MicroThreadCallbackList callbacks = default;

        try
        {
            runRecursion++;
            if (runRecursion == 1)

View on GitHub (pinned to 96fad776d2)