stride3d/stride · error · Exception

Cannot receive out of micro-thread context.

Error message

Cannot receive out of micro-thread context.

What it means

Channel<T>.Receive() awaits data via a micro-thread awaiter; when no senders are pending it needs the ambient MicroThread.Current to register the receiver for wakeup. If called outside any micro-thread, the library throws, because there is no scheduler to resume the continuation.

Solutions

  1. Call Receive only inside code running as a micro-thread (e.g. via Scheduler.Spawn/addScript or Script component)
  2. If you must consume from ordinary code, use a non-blocking check (senders.Count / TryReceive-style polling) or a standard concurrent queue instead
  3. Wrap the consuming logic in a micro-thread that the scheduler runs

Example fix

// before
Task.Run(() => value = channel.Receive());
// after
Scheduler.Spawn(async mt => { value = await channel.Receive(); });
Defensive patterns

Strategy: type-guard

Type guard

bool canReceive = MicroThread.Current != null;

Try / catch

try { value = await channel.Receive(); }
catch (Exception) when (MicroThread.Current == null) { /* fall back to polling or re-run in micro-thread */ }

Prevention

When it happens

Trigger: Calling channel.Receive() from a plain Task, thread-pool thread, main thread, or constructor instead of inside a micro-thread started via Scheduler.

Common situations: Calling Receive in unit tests without a scheduler; consuming a Stride channel from a background Task.Run; awaiting a channel in app startup code outside the Stride script/micro-thread system.

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/2fbb42bef9738cf5. Report an issue: GitHub.

Appendix: source

Thrown at sources/core/Stride.Core.MicroThreading/Channel.cs:89

        {
            receiver.MicroThread.ScheduleContinuation(ScheduleMode.First, receiver.Continuation);
            throw new NotImplementedException();
            //await Scheduler.Yield();
        }
        receiver.IsCompleted = true;
        return receiver;
    }

    /// <summary>
    /// Receives a value over the channel. If no other <see cref="MicroThread"/> is sending data, the receiver will be blocked.
    /// If someone was sending data, which of the sender or receiver continues next depends on <see cref="Preference"/>.
    /// </summary>
    /// <returns>Awaitable data.</returns>
    public ChannelMicroThreadAwaiter<T> Receive()
    {
        if (senders.Count == 0)
        {
            var microThread = MicroThread.Current ?? throw new Exception("Cannot receive out of micro-thread context.");
            var waitingMicroThread = ChannelMicroThreadAwaiter<T>.New(microThread);
            receivers.Enqueue(waitingMicroThread);
            return waitingMicroThread;
        }

        var sender = senders.Dequeue();
        if (Preference == ChannelPreference.PreferReceiver)
        {
            sender.MicroThread.ScheduleContinuation(ScheduleMode.Last, sender.Continuation);
        }
        else if (Preference == ChannelPreference.PreferSender)
        {
            sender.MicroThread.ScheduleContinuation(ScheduleMode.First, sender.Continuation);
            throw new NotImplementedException();
            //await Scheduler.Yield();
        }
        sender.IsCompleted = true;
        return sender;

View on GitHub (pinned to 96fad776d2)