stride3d/stride · error · InvalidOperationException
MicroThread is already completed but still posting…
Error message
MicroThread is already completed but still posting continuations.
What it means
MicroThreadSynchronizationContext.Post schedules a continuation delegate on the micro-thread. If the target micro-thread is already Completed, there is nothing left to resume, so Post throws InvalidOperationException rather than silently dropping the continuation.
Solutions
- Check microThread.State == MicroThreadState.Completed before calling Post
- Do not capture/use the micro-thread's SynchronizationContext after completion; marshal back through the scheduler with a fresh micro-thread if needed
- Fix ownership so continuations are posted before the thread completes (complete sources within the body)
Example fix
// before
syncContext.Post(d, state); // d may run after thread completed
// after
if (microThread.State != MicroThreadState.Completed)
syncContext.Post(d, state); Defensive patterns
Strategy: type-guard
Validate before calling
if (microThread.State == MicroThreadState.Completed) return;
Type guard
bool canPost = microThread.State != MicroThreadState.Completed;
Try / catch
try { context.Post(d, state); }
catch (InvalidOperationException) { /* continuation target completed; drop or reschedule */ } Prevention
- Complete TaskCompletionSources inside the micro-thread body
- Check State before Post
- Don't hold the SynchronizationContext beyond thread lifetime
When it happens
Trigger: Posting continuations (e.g. continuations captured by await/Task sources) to a micro-thread after its body finished — typically a TaskCompletionSource or SynchronizationContext captured earlier and completed after the thread finished.
Common situations: A TaskCompletionSource held by the micro-thread is SetResult'ed later by external code; events firing after the script completed; continuation stored and invoked by a timer or other micro-thread after completion.
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.
- NextFrame cannot be called out of the micro-thread context.
- Already scheduled, call Unschedule before running this…
AI-assisted analysis of stride3d/stride@96fad776d2 (2026-09-14).
Data as JSON: /api/errors/10175d3f49f327b4.
Report an issue: GitHub.
Appendix: source
Thrown at sources/core/Stride.Core.MicroThreading/MicroThreadSynchronizationContext.cs:35
return this;
}
public override void Post(SendOrPostCallback d, object? state)
{
// There is two case:
// 1/ We are either in normal MicroThread inside Scheduler.Step() (CurrentThread test),
// in which case we will directly execute the callback to avoid further processing from scheduler.
// Also, note that Wait() sends us event that are supposed to come back into scheduler.
// Note: As it will end up on the callstack, it might be better to Schedule it instead (to avoid overflow)?
// 2/ Otherwise, we just received an external task continuation (i.e. Task.Sleep()), or a microthread triggering another,
// so schedule it so that it comes back in our regular scheduler.
if (microThread.Scheduler.RunningMicroThread == microThread)
{
d(state);
}
else if (microThread.State == MicroThreadState.Completed)
{
throw new InvalidOperationException("MicroThread is already completed but still posting continuations.");
}
else
{
microThread.ScheduleContinuation(microThread.ScheduleMode, d, state);
}
}
MicroThread IMicroThreadSynchronizationContext.MicroThread => microThread;
}
View on GitHub (pinned to 96fad776d2)