Cysharp/UniTask · error · InvalidOperationException
Can not trigger itself in iterating.
Error message
Can not trigger itself in iterating.
What it means
Thrown by TriggerEvent<T>.SetResult when iteratingNode is non-null, meaning the handler list is mid-iteration and a re-entrant call to SetResult was attempted. UniTask guards against recursive triggering because the linked-list traversal state (iteratingNode) would be corrupted, causing skipped or duplicate handler invocations. The struct deliberately prevents nested dispatch rather than risking undefined iteration behavior.
Source
Thrown at src/UniTask/Assets/Plugins/UniTask/Runtime/TriggerEvent.cs:38
{
ITriggerHandler<T> head; // head.prev is last
ITriggerHandler<T> iteratingHead;
ITriggerHandler<T> iteratingNode;
void LogError(Exception ex)
{
#if UNITY_2018_3_OR_NEWER
UnityEngine.Debug.LogException(ex);
#else
Console.WriteLine(ex);
#endif
}
public void SetResult(T value)
{
if (iteratingNode != null)
{
throw new InvalidOperationException("Can not trigger itself in iterating.");
}
var h = head;
while (h != null)
{
iteratingNode = h;
try
{
h.OnNext(value);
}
catch (Exception ex)
{
LogError(ex);
Remove(h);
}
// If `h` itself is removed by OnNext, h.Next is null.View on GitHub (pinned to ceac8d6946)
Solutions
- Defer the re-entrant SetResult call by scheduling it for the next frame (UniTask.NextFrame or UniTask.Yield) so it runs after iteration completes
- Queue the value and flush it after the current iteration finishes by checking a flag in OnNext and calling SetResult from outside the callback
- Restructure the handler so it does not echo results back into the same TriggerEvent; use a separate TriggerEvent or a queue instead
- If you need pipelining, use UniTask's channel or IUniTaskAsyncEnumerable instead of direct TriggerEvent re-entrant calls
Example fix
// before
void OnNext(T value)
{
triggerEvent.SetResult(transformedValue); // re-entrant -> throws
}
// after
void OnNext(T value)
{
UniTask.NextFrame().ContinueWith(() => triggerEvent.SetResult(transformedValue)).Forget();
} Defensive patterns
Strategy: validation
Validate before calling
// Before calling SetResult, ensure iteration is not active
// TriggerEvent is a struct; callers typically cannot inspect iteratingNode directly.
// Instead, defer re-entrant calls:
void SafeSetResult<T>(TriggerEvent<T> trigger, T value)
{
UniTask.Yield().ContinueWith(() => trigger.SetResult(value)).Forget();
} Try / catch
try
{
triggerEvent.SetResult(value);
}
catch (InvalidOperationException ex) when (ex.Message.Contains("iterating"))
{
// defer to next frame
UniTask.Yield().ContinueWith(() => triggerEvent.SetResult(value)).Forget();
} Prevention
- Never call Set* methods on a TriggerEvent from within its own handler callbacks
- Defer re-entrant triggers using UniTask.Yield or NextFrame
- Document which TriggerEvent is safe to call Set* on from a given callback context
- Consider using IUniTaskAsyncEnumerable/Channel for producer-consumer patterns instead of re-entrant triggers
When it happens
Trigger: Inside an ITriggerHandler<T>.OnNext(T value) callback registered on a TriggerEvent, the handler synchronously calls SetResult on the same TriggerEvent instance. For example, an event handler that immediately tries to push a new value into the same event stream during dispatch.
Common situations: Building reactive event pipelines where a handler forwards or echoes events back to the source TriggerEvent. Using UniTask's player-loop trigger types (e.g., AsyncTrigger, MonoBehaviour triggers) where a callback inadvertently re-invokes the trigger. Custom ITriggerHandler implementations that chain events.
Related errors
- handler
- Delay does not allow minus delayFrameCount. delayFrameCount:
- Delay does not allow minus delayTimeSpan. delayTimeSpan:
- The tasks argument contains no tasks.
- exception
AI-assisted analysis of Cysharp/UniTask@ceac8d6946 (2026-08-13).
Data as JSON: /api/errors/cc6f811b38d2207b.
Report an issue: GitHub.