{"record":{"id":"cc6f811b38d2207b","repo":"Cysharp/UniTask","slug":"can-not-trigger-itself-in-iterating","errorCode":null,"errorMessage":"Can not trigger itself in iterating.","messagePattern":"Can not trigger itself in iterating\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/UniTask/Assets/Plugins/UniTask/Runtime/TriggerEvent.cs","lineNumber":38,"sourceCode":"    {\n        ITriggerHandler<T> head; // head.prev is last\n        ITriggerHandler<T> iteratingHead;\n        ITriggerHandler<T> iteratingNode;\n\n        void LogError(Exception ex)\n        {\n#if UNITY_2018_3_OR_NEWER\n            UnityEngine.Debug.LogException(ex);\n#else\n            Console.WriteLine(ex);\n#endif\n        }\n\n        public void SetResult(T value)\n        {\n            if (iteratingNode != null)\n            {\n                throw new InvalidOperationException(\"Can not trigger itself in iterating.\");\n            }\n\n            var h = head;\n            while (h != null)\n            {\n                iteratingNode = h;\n\n                try\n                {\n                    h.OnNext(value);\n                }\n                catch (Exception ex)\n                {\n                    LogError(ex);\n                    Remove(h);\n                }\n\n                // If `h` itself is removed by OnNext, h.Next is null.","sourceCodeStart":20,"sourceCodeEnd":56,"githubUrl":"https://github.com/Cysharp/UniTask/blob/ceac8d6946b1125fe782cd171fbcb245b567dbf9/src/UniTask/Assets/Plugins/UniTask/Runtime/TriggerEvent.cs#L20-L56","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nvoid OnNext(T value)\n{\n    triggerEvent.SetResult(transformedValue); // re-entrant -> throws\n}\n\n// after\nvoid OnNext(T value)\n{\n    UniTask.NextFrame().ContinueWith(() => triggerEvent.SetResult(transformedValue)).Forget();\n}","handlingStrategy":"validation","validationCode":"// Before calling SetResult, ensure iteration is not active\n// TriggerEvent is a struct; callers typically cannot inspect iteratingNode directly.\n// Instead, defer re-entrant calls:\nvoid SafeSetResult<T>(TriggerEvent<T> trigger, T value)\n{\n    UniTask.Yield().ContinueWith(() => trigger.SetResult(value)).Forget();\n}","typeGuard":null,"tryCatchPattern":"try\n{\n    triggerEvent.SetResult(value);\n}\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"iterating\"))\n{\n    // defer to next frame\n    UniTask.Yield().ContinueWith(() => triggerEvent.SetResult(value)).Forget();\n}","preventionTips":["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"],"tags":["unitask","trigger-event","reentrancy","unity","csharp"],"backgroundTag":null,"analyzedSha":"ceac8d6946b1125fe782cd171fbcb245b567dbf9","analyzedAt":"2026-08-13T19:16:39.025Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}